1. Role-Based Access Control (RBAC) với Casbin
1.1. Setup
go get github.com/casbin/casbin/v2
go get github.com/casbin/gorm-adapter/v3
1.2. Model Configuration
# config/rbac_model.conf
[request_definition]
r = sub, obj, act
[policy_definition]
p = sub, obj, act
[role_definition]
g = _, _
[policy_effect]
e = some(where (p.eft == allow))
[matchers]
m = g(r.sub, p.sub) && keyMatch2(r.obj, p.obj) && r.act == p.act
- sub: subject (user role)
- obj: object (API path)
- act: action (HTTP method)
1.3. Policy Rules
# config/rbac_policy.csv
p, admin, /api/*, GET
p, admin, /api/*, POST
p, admin, /api/*, PUT
p, admin, /api/*, DELETE
p, admin, /admin/*, GET
p, admin, /admin/*, POST
p, editor, /api/posts, GET
p, editor, /api/posts, POST
p, editor, /api/posts/:id, PUT
p, editor, /api/posts/:id, DELETE
p, user, /api/posts, GET
p, user, /api/profile, GET
p, user, /api/profile, PUT
g, admin, editor
g, editor, user
1.4. Casbin Middleware
package middleware
import (
"net/http"
"github.com/casbin/casbin/v2"
"github.com/gin-gonic/gin"
)
func CasbinMiddleware(enforcer *casbin.Enforcer) gin.HandlerFunc {
return func(c *gin.Context) {
role := GetUserRole(c)
path := c.Request.URL.Path
method := c.Request.Method
allowed, err := enforcer.Enforce(role, path, method)
if err != nil {
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{
"error": "Authorization error",
})
return
}
if !allowed {
c.AbortWithStatusJSON(http.StatusForbidden, gin.H{
"error": "Access denied",
})
return
}
c.Next()
}
}
// Setup enforcer
func SetupCasbin() (*casbin.Enforcer, error) {
enforcer, err := casbin.NewEnforcer("config/rbac_model.conf", "config/rbac_policy.csv")
if err != nil {
return nil, err
}
// Load policies
if err := enforcer.LoadPolicy(); err != nil {
return nil, err
}
return enforcer, nil
}
2. CORS Configuration
go get github.com/gin-contrib/cors
import "github.com/gin-contrib/cors"
func SetupCORS(r *gin.Engine) {
config := cors.Config{
AllowOrigins: []string{"https://example.com", "https://admin.example.com"},
AllowMethods: []string{"GET", "POST", "PUT", "DELETE", "OPTIONS"},
AllowHeaders: []string{"Origin", "Content-Type", "Authorization"},
ExposeHeaders: []string{"Content-Length"},
AllowCredentials: true,
MaxAge: 12 * time.Hour, // Preflight cache
}
r.Use(cors.New(config))
}
// ⚠️ KHÔNG dùng AllowAllOrigins: true trong production
// ⚠️ KHÔNG dùng AllowOrigins: []string{"*"} khi AllowCredentials: true
3. Rate Limiting
package middleware
import (
"net/http"
"sync"
"time"
"github.com/gin-gonic/gin"
"golang.org/x/time/rate"
)
type IPRateLimiter struct {
mu sync.RWMutex
limiters map[string]*rate.Limiter
rate rate.Limit
burst int
}
func NewIPRateLimiter(r rate.Limit, burst int) *IPRateLimiter {
return &IPRateLimiter{
limiters: make(map[string]*rate.Limiter),
rate: r,
burst: burst,
}
}
func (rl *IPRateLimiter) GetLimiter(ip string) *rate.Limiter {
rl.mu.Lock()
defer rl.mu.Unlock()
limiter, exists := rl.limiters[ip]
if !exists {
limiter = rate.NewLimiter(rl.rate, rl.burst)
rl.limiters[ip] = limiter
}
return limiter
}
func RateLimitMiddleware(rps float64, burst int) gin.HandlerFunc {
limiter := NewIPRateLimiter(rate.Limit(rps), burst)
// Cleanup old entries periodically
go func() {
for range time.Tick(10 * time.Minute) {
limiter.mu.Lock()
limiter.limiters = make(map[string]*rate.Limiter)
limiter.mu.Unlock()
}
}()
return func(c *gin.Context) {
ip := c.ClientIP()
l := limiter.GetLimiter(ip)
if !l.Allow() {
c.AbortWithStatusJSON(http.StatusTooManyRequests, gin.H{
"error": "Too many requests. Please try again later.",
})
return
}
c.Next()
}
}
// Usage: r.Use(RateLimitMiddleware(10, 20)) // 10 req/s, burst 20
4. Security Headers
func SecurityHeadersMiddleware() gin.HandlerFunc {
return func(c *gin.Context) {
c.Header("X-Content-Type-Options", "nosniff")
c.Header("X-Frame-Options", "DENY")
c.Header("X-XSS-Protection", "1; mode=block")
c.Header("Strict-Transport-Security", "max-age=31536000; includeSubDomains")
c.Header("Content-Security-Policy", "default-src 'self'")
c.Header("Referrer-Policy", "strict-origin-when-cross-origin")
c.Header("Permissions-Policy", "camera=(), microphone=(), geolocation=()")
c.Next()
}
}
5. Input Validation
package validator
import (
"regexp"
"github.com/go-playground/validator/v10"
)
// Custom validators
func RegisterCustomValidators(v *validator.Validate) {
// Username: chữ, số, gạch dưới, 3-30 ký tự
v.RegisterValidation("username", func(fl validator.FieldLevel) bool {
re := regexp.MustCompile(`^[a-zA-Z0-9_]{3,30}$`)
return re.MatchString(fl.Field().String())
})
// No HTML tags (XSS prevention)
v.RegisterValidation("noscript", func(fl validator.FieldLevel) bool {
re := regexp.MustCompile(`<[^>]*>`)
return !re.MatchString(fl.Field().String())
})
// Vietnamese phone number
v.RegisterValidation("vnphone", func(fl validator.FieldLevel) bool {
re := regexp.MustCompile(`^(\+84|0)(3|5|7|8|9)\d{8}$`)
return re.MatchString(fl.Field().String())
})
}
// Input structs with validation
type CreatePostInput struct {
Title string `json:"title" binding:"required,min=5,max=200,noscript"`
Content string `json:"content" binding:"required,min=50"`
Tags []string `json:"tags" binding:"max=10,dive,min=1,max=50"`
}
6. SQL Injection Prevention
// ❌ NGUY HIỂM - SQL Injection
db.Raw("SELECT * FROM users WHERE email = '" + email + "'")
// ✅ AN TOÀN - Parameterized queries
db.Raw("SELECT * FROM users WHERE email = ?", email)
// ✅ AN TOÀN - GORM methods
db.Where("email = ?", email).First(&user)
// ✅ AN TOÀN - Named parameters
db.Where("email = @email AND role = @role", map[string]interface{}{
"email": email, "role": role,
}).First(&user)
7. Middleware Chain Hoàn chỉnh
func SetupRouter(tokenService *auth.TokenService, enforcer *casbin.Enforcer) *gin.Engine {
r := gin.New()
// Global middleware (thứ tự quan trọng!)
r.Use(gin.Recovery()) // 1. Recover panics
r.Use(SecurityHeadersMiddleware()) // 2. Security headers
r.Use(SetupCORS(r)) // 3. CORS
r.Use(RequestIDMiddleware()) // 4. Request ID
r.Use(LoggingMiddleware()) // 5. Structured logging
r.Use(RateLimitMiddleware(100, 200)) // 6. Rate limit
// Public routes
public := r.Group("/auth")
public.Use(RateLimitMiddleware(5, 10)) // Stricter rate limit for auth
{
public.POST("/login", authHandler.Login)
public.POST("/register", authHandler.Register)
public.POST("/refresh", authHandler.RefreshToken)
}
// Protected routes
api := r.Group("/api")
api.Use(AuthMiddleware(tokenService)) // Auth required
api.Use(CasbinMiddleware(enforcer)) // RBAC check
{
api.GET("/profile", getProfile)
api.PUT("/profile", updateProfile)
api.GET("/posts", listPosts)
api.POST("/posts", createPost)
}
return r
}
8. Request ID Middleware
import "github.com/google/uuid"
func RequestIDMiddleware() gin.HandlerFunc {
return func(c *gin.Context) {
requestID := c.GetHeader("X-Request-ID")
if requestID == "" {
requestID = uuid.New().String()
}
c.Set("request_id", requestID)
c.Header("X-Request-ID", requestID)
c.Next()
}
}
9. Tổng kết
| Layer | Mục đích | Tool |
|---|---|---|
| Authentication | Xác thực user | JWT + bcrypt |
| Authorization | Phân quyền | Casbin RBAC |
| Rate Limiting | Chống DDoS/brute force | golang.org/x/time/rate |
| Input Validation | Validate & sanitize input | go-playground/validator |
| SQL Prevention | Chống SQL injection | GORM parameterized queries |
| Headers | Security headers | Custom middleware |
| CORS | Cross-origin control | gin-contrib/cors |
Bài tiếp theo: WebSockets & Real-time Communication — gorilla/websocket, chat rooms, và broadcast patterns.