Chuyển tới nội dung chính

🔐 An Toàn Vệ Sinh - Security


Trong nhà hàng, an toàn vệ sinh thực phẩm là yếu tố sống còn - một sơ suất nhỏ có thể khiến thực khách gặp nguy hiểm. Tương tự, trong lập trình Go, security đảm bảo ứng dụng của bạn an toàn trước các mối đe dọa.

🔍 Kiểm Tra Nguyên Liệu - Input Validation

Trước khi chế biến, đầu bếp luôn kiểm tra nguyên liệu kỹ lưỡng - loại bỏ thực phẩm hỏng, rửa sạch rau củ. Trong Go, input validation là quá trình kiểm tra dữ liệu đầu vào.

Ví dụ: Kiểm tra nguyên liệu đầu vào

package main

import (
"fmt"
"regexp"
"strings"
)

// Kiểm tra email hợp lệ
func validateEmail(email string) bool {
// Pattern kiểm tra email
pattern := `^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$`
regex := regexp.MustCompile(pattern)
return regex.MatchString(email)
}

// Kiểm tra username (chỉ chữ cái, số, gạch dưới)
func validateUsername(username string) bool {
if len(username) < 3 || len(username) > 20 {
return false
}
pattern := `^[a-zA-Z0-9_]+$`
regex := regexp.MustCompile(pattern)
return regex.MatchString(username)
}

// Làm sạch input (sanitization)
func sanitizeInput(input string) string {
// Loại bỏ khoảng trắng thừa
input = strings.TrimSpace(input)
// Loại bỏ ký tự đặc biệt nguy hiểm
dangerous := []string{"<script>", "</script>", "<", ">", "eval(", "javascript:"}
for _, char := range dangerous {
input = strings.ReplaceAll(input, char, "")
}
return input
}

func main() {
// Test validation
emails := []string{"[email protected]", "invalid.email", "[email protected]"}

fmt.Println("🔍 Kiểm tra nguyên liệu (email):")
for _, email := range emails {
if validateEmail(email) {
fmt.Printf("✅ %s - Nguyên liệu tốt\n", email)
} else {
fmt.Printf("❌ %s - Nguyên liệu kém chất lượng\n", email)
}
}

// Test sanitization
userInput := " <script>alert('xss')</script>Hello World "
cleaned := sanitizeInput(userInput)
fmt.Printf("\n🧹 Làm sạch nguyên liệu:\n")
fmt.Printf("Trước: '%s'\n", userInput)
fmt.Printf("Sau: '%s'\n", cleaned)
}

Đầu ra:

🔍 Kiểm tra nguyên liệu (email):
[email protected] - Nguyên liệu tốt
❌ invalid.email - Nguyên liệu kém chất lượng
[email protected] - Nguyên liệu tốt

🧹 Làm sạch nguyên liệu:
Trước: ' <script>alert('xss')</script>Hello World '
Sau: 'Hello World'

🧪 Nguyên Liệu Độc Hại - SQL Injection Prevention

Nguyên liệu độc hại trong bếp có thể gây ngộ độc thực phẩm. Trong lập trình, SQL Injection là khi kẻ tấn công "đầu độc" câu truy vấn database của bạn.

Ví dụ: Phòng chống nguyên liệu độc hại

package main

import (
"database/sql"
"fmt"
"log"
_ "github.com/mattn/go-sqlite3"
)

// ❌ KHÔNG AN TOÀN - Dễ bị SQL Injection
func unsafeQuery(db *sql.DB, username string) {
query := fmt.Sprintf("SELECT * FROM users WHERE username = '%s'", username)
rows, err := db.Query(query)
if err != nil {
log.Fatal(err)
}
defer rows.Close()

fmt.Println("Query không an toàn:", query)
}

// ✅ AN TOÀN - Sử dụng Prepared Statements
func safeQuery(db *sql.DB, username string) {
query := "SELECT id, username, email FROM users WHERE username = ?"
rows, err := db.Query(query, username)
if err != nil {
log.Fatal(err)
}
defer rows.Close()

fmt.Println("✅ Query an toàn với prepared statement")

for rows.Next() {
var id int
var user, email string
rows.Scan(&id, &user, &email)
fmt.Printf("User: %s (%s)\n", user, email)
}
}

func main() {
// Tạo database demo
db, err := sql.Open("sqlite3", ":memory:")
if err != nil {
log.Fatal(err)
}
defer db.Close()

// Tạo bảng users
db.Exec(`CREATE TABLE users (
id INTEGER PRIMARY KEY,
username TEXT,
email TEXT
)`)

// Thêm dữ liệu mẫu
db.Exec("INSERT INTO users (username, email) VALUES ('chef', '[email protected]')")
db.Exec("INSERT INTO users (username, email) VALUES ('waiter', '[email protected]')")

// Input nguy hiểm (SQL Injection attempt)
maliciousInput := "chef' OR '1'='1"

fmt.Println("🧪 Test với input độc hại:", maliciousInput)
fmt.Println()

// Unsafe query sẽ trả về TẤT CẢ users
fmt.Println("❌ Cách không an toàn:")
unsafeQuery(db, maliciousInput)

fmt.Println("\n✅ Cách an toàn:")
safeQuery(db, maliciousInput) // Không tìm thấy gì vì input được escape
}

🎫 Kiểm Tra Nhân Viên - Authentication

Nhà hàng chỉ cho phép nhân viên có thẻ vào khu vực bếp. Trong Go, authentication xác minh danh tính người dùng.

Ví dụ: Hệ thống thẻ nhân viên (JWT Authentication)

package main

import (
"fmt"
"time"
"crypto/hmac"
"crypto/sha256"
"encoding/base64"
"encoding/json"
"strings"
)

// Thông tin nhân viên
type StaffClaims struct {
Username string `json:"username"`
Role string `json:"role"`
Exp int64 `json:"exp"`
}

// Secret key (trong thực tế phải lưu an toàn)
var secretKey = []byte("restaurant-secret-key-2024")

// Tạo thẻ nhân viên (JWT token)
func createStaffCard(username, role string) (string, error) {
// Header
header := map[string]string{
"alg": "HS256",
"typ": "JWT",
}
headerJSON, _ := json.Marshal(header)
headerB64 := base64.RawURLEncoding.EncodeToString(headerJSON)

// Payload (claims)
claims := StaffClaims{
Username: username,
Role: role,
Exp: time.Now().Add(24 * time.Hour).Unix(), // Hết hạn sau 24h
}
claimsJSON, _ := json.Marshal(claims)
claimsB64 := base64.RawURLEncoding.EncodeToString(claimsJSON)

// Signature
message := headerB64 + "." + claimsB64
signature := createSignature(message)

// JWT = header.payload.signature
token := message + "." + signature
return token, nil
}

// Tạo chữ ký
func createSignature(message string) string {
h := hmac.New(sha256.New, secretKey)
h.Write([]byte(message))
return base64.RawURLEncoding.EncodeToString(h.Sum(nil))
}

// Kiểm tra thẻ nhân viên
func verifyStaffCard(token string) (*StaffClaims, error) {
parts := strings.Split(token, ".")
if len(parts) != 3 {
return nil, fmt.Errorf("❌ Thẻ không hợp lệ")
}

// Verify signature
message := parts[0] + "." + parts[1]
expectedSig := createSignature(message)
if parts[2] != expectedSig {
return nil, fmt.Errorf("❌ Thẻ giả mạo")
}

// Decode claims
claimsJSON, _ := base64.RawURLEncoding.DecodeString(parts[1])
var claims StaffClaims
json.Unmarshal(claimsJSON, &claims)

// Check expiration
if time.Now().Unix() > claims.Exp {
return nil, fmt.Errorf("❌ Thẻ đã hết hạn")
}

return &claims, nil
}

func main() {
fmt.Println("🎫 Hệ Thống Thẻ Nhân Viên (JWT Authentication)\n")

// Cấp thẻ cho đầu bếp
chefCard, _ := createStaffCard("chef_john", "chef")
fmt.Println("✅ Cấp thẻ cho đầu bếp John")
fmt.Println("Thẻ:", chefCard[:50]+"...")

// Cấp thẻ cho phục vụ
waiterCard, _ := createStaffCard("waiter_mary", "waiter")
fmt.Println("\n✅ Cấp thẻ cho phục vụ Mary")
fmt.Println("Thẻ:", waiterCard[:50]+"...")

// Kiểm tra thẻ
fmt.Println("\n🔍 Kiểm tra thẻ nhân viên:")

claims, err := verifyStaffCard(chefCard)
if err != nil {
fmt.Println(err)
} else {
fmt.Printf("✅ Thẻ hợp lệ - Nhân viên: %s, Vị trí: %s\n", claims.Username, claims.Role)
}

// Test với thẻ giả
fakeCard := "fake.token.here"
fmt.Println("\n🔍 Kiểm tra thẻ giả:")
_, err = verifyStaffCard(fakeCard)
if err != nil {
fmt.Println(err)
}
}

🔒 Niêm Phong Món Ăn - Encryption

Khi giao món ăn cao cấp, nhà hàng niêm phong để đảm bảo không bị can thiệp. Trong Go, encryption bảo vệ dữ liệu nhạy cảm.

Ví dụ: Niêm phong thông tin (AES Encryption)

package main

import (
"crypto/aes"
"crypto/cipher"
"crypto/rand"
"encoding/hex"
"fmt"
"io"
)

// Niêm phong dữ liệu (encrypt)
func sealData(plaintext string, key []byte) (string, error) {
block, err := aes.NewCipher(key)
if err != nil {
return "", err
}

// GCM mode (Galois/Counter Mode)
gcm, err := cipher.NewGCM(block)
if err != nil {
return "", err
}

// Tạo nonce (number used once)
nonce := make([]byte, gcm.NonceSize())
io.ReadFull(rand.Reader, nonce)

// Encrypt
ciphertext := gcm.Seal(nonce, nonce, []byte(plaintext), nil)
return hex.EncodeToString(ciphertext), nil
}

// Mở niêm phong (decrypt)
func unsealData(cipherHex string, key []byte) (string, error) {
ciphertext, _ := hex.DecodeString(cipherHex)

block, err := aes.NewCipher(key)
if err != nil {
return "", err
}

gcm, err := cipher.NewGCM(block)
if err != nil {
return "", err
}

nonceSize := gcm.NonceSize()
nonce, ciphertext := ciphertext[:nonceSize], ciphertext[nonceSize:]

plaintext, err := gcm.Open(nil, nonce, ciphertext, nil)
if err != nil {
return "", err
}

return string(plaintext), nil
}

func main() {
// Key phải 16, 24, hoặc 32 bytes (AES-128, AES-192, AES-256)
key := []byte("restaurant-key32bytes-secure") // 28 bytes, thêm 4 để đủ 32
key = append(key, []byte("1234")...)

fmt.Println("🔒 Hệ Thống Niêm Phong Món Ăn (AES Encryption)\n")

// Thông tin nhạy cảm cần bảo vệ
secretRecipe := "Công thức bí mật: 500g bột, 3 quả trứng, 100ml sữa"

fmt.Println("📝 Công thức gốc:", secretRecipe)

// Niêm phong
sealed, err := sealData(secretRecipe, key)
if err != nil {
fmt.Println("Lỗi niêm phong:", err)
return
}

fmt.Println("\n🔒 Công thức đã niêm phong:")
fmt.Println(sealed)

// Mở niêm phong
unsealed, err := unsealData(sealed, key)
if err != nil {
fmt.Println("Lỗi mở niêm phong:", err)
return
}

fmt.Println("\n🔓 Công thức đã mở niêm phong:")
fmt.Println(unsealed)

// Test với key sai
fmt.Println("\n❌ Thử mở với key sai:")
wrongKey := []byte("wrong-key-32bytes-xxxxxxxxxxxxx")
_, err = unsealData(sealed, wrongKey)
if err != nil {
fmt.Println("Không thể mở niêm phong - Key không đúng!")
}
}

🔑 Mã Hóa Mật Khẩu - Password Hashing

Nhà hàng không lưu mã két gốc mà chỉ lưu dấu vân tay của chìa khóa. Tương tự, không bao giờ lưu mật khẩu dạng plaintext, mà phải hash.

Ví dụ: Mã hóa mật khẩu với bcrypt

package main

import (
"fmt"
"golang.org/x/crypto/bcrypt"
)

// Tạo mã hóa mật khẩu (dấu vân tay)
func hashPassword(password string) (string, error) {
bytes, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
return string(bytes), err
}

// Kiểm tra mật khẩu
func checkPassword(password, hash string) bool {
err := bcrypt.CompareHashAndPassword([]byte(hash), []byte(password))
return err == nil
}

func main() {
fmt.Println("🔑 Hệ Thống Mã Hóa Mật Khẩu (bcrypt)\n")

// Mật khẩu của đầu bếp
password := "ChefSecret123!"

// Tạo hash (dấu vân tay)
hash, err := hashPassword(password)
if err != nil {
fmt.Println("Lỗi mã hóa:", err)
return
}

fmt.Println("🔐 Mật khẩu gốc:", password)
fmt.Println("🔏 Dấu vân tay (hash):")
fmt.Println(hash)

// Kiểm tra mật khẩu đúng
fmt.Println("\n✅ Test với mật khẩu đúng:")
if checkPassword(password, hash) {
fmt.Println("Đăng nhập thành công!")
} else {
fmt.Println("Mật khẩu sai!")
}

// Kiểm tra mật khẩu sai
fmt.Println("\n❌ Test với mật khẩu sai:")
if checkPassword("WrongPassword", hash) {
fmt.Println("Đăng nhập thành công!")
} else {
fmt.Println("Mật khẩu sai!")
}

// Mỗi lần hash cho kết quả khác nhau (vì có salt)
fmt.Println("\n🔄 Hash cùng mật khẩu 2 lần:")
hash1, _ := hashPassword(password)
hash2, _ := hashPassword(password)
fmt.Println("Hash 1:", hash1[:30]+"...")
fmt.Println("Hash 2:", hash2[:30]+"...")
fmt.Println("Giống nhau?", hash1 == hash2)
fmt.Println("Nhưng cả 2 đều verify được mật khẩu gốc!")
}

🛡️ HTTPS - Giao Hàng An Toàn

Khi giao món ăn, nhà hàng dùng xe chuyên dụng có khóa thay vì xe tay không bảo vệ. Trong web, HTTPS là "xe giao hàng an toàn" thay vì HTTP không mã hóa.

Ví dụ: Server HTTPS

package main

import (
"fmt"
"log"
"net/http"
)

func homeHandler(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, `
<!DOCTYPE html>
<html>
<head>
<title>🍽️ Nhà Hàng An Toàn</title>
</head>
<body>
<h1>🔒 Kết nối HTTPS an toàn!</h1>
<p>Món ăn của bạn được giao bằng xe chuyên dụng có khóa 🚚🔐</p>
</body>
</html>
`)
}

func main() {
http.HandleFunc("/", homeHandler)

fmt.Println("🛡️ Server HTTPS đang chạy tại https://localhost:8443")
fmt.Println("📝 Lưu ý: Cần tạo certificate trước khi chạy:")
fmt.Println(" openssl req -x509 -newkey rsa:4096 -keyout key.pem -out cert.pem -days 365 -nodes")

// Chạy server HTTPS
err := http.ListenAndServeTLS(":8443", "cert.pem", "key.pem", nil)
if err != nil {
log.Fatal("❌ Lỗi HTTPS:", err)
}
}

// Nếu chưa có certificate, chạy HTTP thông thường:
// func main() {
// http.HandleFunc("/", homeHandler)
// fmt.Println("⚠️ Server HTTP (không an toàn) tại http://localhost:8080")
// http.ListenAndServe(":8080", nil)
// }

🚫 CORS - Kiểm Soát Cửa Ra Vào

Nhà hàng không cho phép khách từ nhà hàng khác vào bếp tùy tiện. CORS (Cross-Origin Resource Sharing) kiểm soát domain nào được phép gọi API của bạn.

Ví dụ: CORS Middleware

package main

import (
"fmt"
"log"
"net/http"
)

// CORS Middleware - Kiểm soát cửa ra vào
func corsMiddleware(next http.HandlerFunc) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
// Chỉ cho phép domain này truy cập
allowedOrigins := []string{
"https://restaurant.com",
"https://www.restaurant.com",
}

origin := r.Header.Get("Origin")
allowed := false

for _, allowedOrigin := range allowedOrigins {
if origin == allowedOrigin {
allowed = true
w.Header().Set("Access-Control-Allow-Origin", origin)
break
}
}

if !allowed && origin != "" {
fmt.Printf("❌ Chặn truy cập từ: %s\n", origin)
http.Error(w, "Origin not allowed", http.StatusForbidden)
return
}

w.Header().Set("Access-Control-Allow-Methods", "GET, POST, OPTIONS")
w.Header().Set("Access-Control-Allow-Headers", "Content-Type, Authorization")

// Handle preflight request
if r.Method == "OPTIONS" {
w.WriteHeader(http.StatusOK)
return
}

next(w, r)
}
}

func menuHandler(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, `{"menu": ["Phở", "Bún chả", "Cơm tấm"]}`)
}

func main() {
http.HandleFunc("/api/menu", corsMiddleware(menuHandler))

fmt.Println("🚫 Server với CORS protection đang chạy tại :8080")
fmt.Println("✅ Cho phép: https://restaurant.com")
fmt.Println("❌ Chặn: các domain khác")

log.Fatal(http.ListenAndServe(":8080", nil))
}

⏱️ Rate Limiting - Giới Hạn Khách

Nhà hàng không cho phép một khách order quá nhiều trong thời gian ngắn (có thể là spam). Rate limiting ngăn chặn abuse.

Ví dụ: Rate Limiting Middleware

package main

import (
"fmt"
"net/http"
"sync"
"time"
)

// Rate limiter cho từng IP
type RateLimiter struct {
visitors map[string]*Visitor
mu sync.Mutex
limit int // Số request tối đa
window time.Duration // Trong khoảng thời gian
}

type Visitor struct {
lastSeen time.Time
count int
}

func NewRateLimiter(limit int, window time.Duration) *RateLimiter {
rl := &RateLimiter{
visitors: make(map[string]*Visitor),
limit: limit,
window: window,
}

// Cleanup visitors cũ mỗi phút
go rl.cleanupVisitors()
return rl
}

func (rl *RateLimiter) isAllowed(ip string) bool {
rl.mu.Lock()
defer rl.mu.Unlock()

visitor, exists := rl.visitors[ip]
now := time.Now()

if !exists {
rl.visitors[ip] = &Visitor{lastSeen: now, count: 1}
return true
}

// Reset counter nếu đã qua window
if now.Sub(visitor.lastSeen) > rl.window {
visitor.count = 1
visitor.lastSeen = now
return true
}

// Kiểm tra limit
if visitor.count >= rl.limit {
return false
}

visitor.count++
visitor.lastSeen = now
return true
}

func (rl *RateLimiter) cleanupVisitors() {
ticker := time.NewTicker(1 * time.Minute)
for range ticker.C {
rl.mu.Lock()
for ip, visitor := range rl.visitors {
if time.Since(visitor.lastSeen) > rl.window {
delete(rl.visitors, ip)
}
}
rl.mu.Unlock()
}
}

// Middleware
func (rl *RateLimiter) Limit(next http.HandlerFunc) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
ip := r.RemoteAddr

if !rl.isAllowed(ip) {
fmt.Printf("⏱️ Rate limit exceeded for IP: %s\n", ip)
http.Error(w, "Too many requests. Please try again later.", http.StatusTooManyRequests)
return
}

next(w, r)
}
}

func orderHandler(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "✅ Order received successfully!")
}

func main() {
// Rate limiter: tối đa 5 requests / 1 phút
limiter := NewRateLimiter(5, 1*time.Minute)

http.HandleFunc("/order", limiter.Limit(orderHandler))

fmt.Println("⏱️ Server với Rate Limiting đang chạy tại :8080")
fmt.Println("📊 Giới hạn: 5 requests / phút / IP")
fmt.Println("🧪 Test: curl http://localhost:8080/order (lặp lại >5 lần)")

http.ListenAndServe(":8080", nil)
}

📋 Tổng Kết

Khái niệm SecurityẨn dụ Nhà hàngMục đích
Input ValidationKiểm tra nguyên liệuĐảm bảo dữ liệu đầu vào hợp lệ
SQL InjectionNguyên liệu độc hạiPhòng chống tấn công database
AuthenticationKiểm tra nhân viênXác minh danh tính người dùng
EncryptionNiêm phong món ănBảo vệ dữ liệu nhạy cảm
Password HashingMã hóa mật khẩuLưu mật khẩu an toàn
HTTPSGiao hàng an toànMã hóa kết nối
CORSKiểm soát cửa ra vàoChặn truy cập không hợp lệ
Rate LimitingGiới hạn kháchNgăn chặn spam/abuse

🎯 Nguyên tắc Security

  1. Never trust user input - Luôn kiểm tra và sanitize
  2. Use prepared statements - Phòng chống SQL injection
  3. Hash passwords - Không lưu plaintext
  4. Use HTTPS - Mã hóa kết nối
  5. Implement rate limiting - Ngăn chặn abuse
  6. Keep secrets secret - Không commit API keys, passwords
  7. Update dependencies - Vá lỗi bảo mật kịp thời
  8. Principle of least privilege - Chỉ cấp quyền tối thiểu cần thiết

Chúc mừng! Bạn đã hoàn thành toàn bộ khóa học Go! 🎉

Giống như một nhà hàng cần an toàn vệ sinh để bảo vệ thực khách, ứng dụng của bạn cần security để bảo vệ người dùng. Hãy luôn đặt bảo mật lên hàng đầu trong mọi dự án! 🔐