1. 使用 Casbin 的角色為基礎的存取控制 (RBAC)
1.1.設置
go get github.com/casbin/casbin/v2
go get github.com/casbin/gorm-adapter/v3
1.2.型號配置
# 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
- 子:主題(使用者角色)
- 物件: 物件(API 路徑)
- 行動。行動:操作(HTTP 方法)
1.3.政策規則
# 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中介軟體
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配置
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. 速率限制
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. 安全標頭
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. 輸入驗證
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注入預防
// ❌ 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. 完整的中介軟體鏈
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. 請求ID中介軟體
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. 總結
| 圖層 | 目的 | 工具 |
|---|---|---|
| 認證 | 使用者認證 | JWT + bcrypt |
| 授權 | 去中心化 | RBAC 卡賓 |
| 速率限制 | 抗DDoS/暴力破解 | golang.org/x/time/rate |
| 輸入驗證 | 驗證和清理輸入 | go-playground/驗證器 |
| SQL預防 | 防止SQL注入 | GORM 參數化查詢 |
| 標頭 | 安全標頭 | 客製化中間件 |
| 跨域資源共享 | 跨域控制 | gin-contrib/cors |
下一篇: WebSocket 和即時通信 — gorilla/websocket、聊天室和廣播模式。