iT邦幫忙

2025 iThome 鐵人賽

DAY 11
0

前言

我們在前一篇,說明了使用 DynamoDB 的好處後,這邊就是來實作了。

原始碼

註冊 API (/register) Lambda Function

  • 在下面原始碼中,要將第四行 table name 改成你自己的喔
  • 使用者如何註冊? 先告訴我新帳號長怎樣密碼長怎樣⋯⋯
import json, boto3, bcrypt

dynamodb = boto3.resource("dynamodb")
table = dynamodb.Table("vlog-nipapa-tw-user")

def lambda_handler(event, context):
    body = json.loads(event["body"])
    username = body["username"]
    password = body["password"]

    # 密碼 Hash
    pw_hash = bcrypt.hashpw(password.encode("utf-8"), bcrypt.gensalt()).decode("utf-8")

    # 存入 DynamoDB
    table.put_item(Item={"username": username, "password_hash": pw_hash})

    return {"statusCode": 200, "body": json.dumps({"message": "User registered"})}

  • 這邊有個微妙的地方欸,就是什麼時候將新密碼給 Hash ? 不應該是前端頁面嗎!!? 但我們這邊的邏輯寫成,使用者輸入 明碼密碼 → 透過 HTTPS 傳給後端 → 後端 (Lambda) 負責 bcrypt.hashpw(...) → 存進 DynamoDB。 原因有幾個⋯⋯
  1. 信任邊界 (Trust Boundary)
    • 前端程式碼是公開的,任何人都可以打開 login.html、F12 查看程式碼。
    • 如果你在前端 hash → 攻擊者照樣能看到 hash 邏輯,甚至自己生成 hash 丟給 API。
    • 這樣你的系統相當於 明碼傳輸 = Hash 傳輸,沒有安全提升。
  2. 後端才能保證一致性
    • bcrypt 有 salt,每次結果都不一樣。
    • 前端算 hash,怎麼確保大家用同一組 salt?(很難安全分發 salt)
  3. HTTPS 已經保護傳輸安全
    • 只要 API Gateway → Lambda 用的是 HTTPS,明碼不會裸奔在網路上。
    • 所以密碼傳給 Lambda,再做 Hash 是安全的。
  4. 密碼驗證需要後端邏輯
    • 登入時要 bcrypt.checkpw(password, pw_hash),這個只能在後端做,因為資料庫裡存的就是 hash。
  • 前端:只負責輸入密碼,交給 API(必須用 HTTPS!)。
  • Lambda:
    > 註冊:bcrypt.hashpw() → 存 hash
    > 登入:bcrypt.checkpw() → 驗證
  • 永遠不要在 DB 裡存前端算好的 hash。

登入 API (/login) Lambda Function

import json, boto3, bcrypt, jwt, os

SECRET = os.environ.get("JWT_SECRET", "mysecret")
dynamodb = boto3.resource("dynamodb")
table = dynamodb.Table("vlog-nipapa-tw-user")

def lambda_handler(event, context):
    body = json.loads(event["body"])
    username = body["username"]
    password = body["password"]

    # 查 DB
    resp = table.get_item(Key={"username": username})
    if "Item" not in resp:
        return {"statusCode": 401, "body": json.dumps({"error": "User not found"})}

    pw_hash = resp["Item"]["password_hash"]

    if not bcrypt.checkpw(password.encode("utf-8"), pw_hash.encode("utf-8")):
        return {"statusCode": 401, "body": json.dumps({"error": "Invalid password"})}

    # 產生 JWT
    token = jwt.encode({"username": username}, SECRET, algorithm="HS256")

    return {"statusCode": 200, "body": json.dumps({"token": token})}

登入頁面 login.html

<!DOCTYPE html>
<html>
<head>
  <title>Login</title>
</head>
<body>
  <h2>Login</h2>
  <form id="loginForm">
    <input type="text" id="username" placeholder="Username" required><br>
    <input type="password" id="password" placeholder="Password" required><br>
    <button type="submit">Login</button>
  </form>
  <script>
    document.getElementById("loginForm").addEventListener("submit", async (e) => {
      e.preventDefault();
      const username = document.getElementById("username").value;
      const password = document.getElementById("password").value;

      const res = await fetch("https://vlog.nipapa.tw/prod/login", {
        method: "POST",
        headers: {"Content-Type": "application/json"},
        body: JSON.stringify({ username, password })
      });

      const data = await res.json();
      if (data.token) {
        localStorage.setItem("jwt", data.token);
        window.location.href = "main.html";  // 成功跳轉
      } else {
        alert("Login failed");
      }
    });
  </script>
</body>
</html>

結論

  • 這邊開發完登入頁面了,剩下的東西就有需要再陸續增加。
  • 下一回,會開始探索 AWS Media 系列的服務。

上一篇
【Day 10】 製作無伺服器的會員系統 - DynamoDB 的使用入門
系列文
無法成為片師也想拍 Vlog?!個人影音小工具的誕生!11
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言