JWT middleware
Auth middleware yozish darsida net/http uchun JWT middleware yozilgan edi. Bu darsda xuddi shu mantiqni Gin’ga ko‘chiramiz. Tokenni tekshirish qoidalari o‘zgarmaydi. Faqat middleware’ning shakli, foydalanuvchini saqlash usuli va marshrutlarni himoyalash yo‘li o‘zgaradi.
Token yaratish va tekshirish uchun JWT yaratish va tekshirish darsidagi TokenService ishlatiladi. U HTTP haqida hech narsa bilmaydi, shuning uchun Gin bilan ham o‘zgarishsiz ishlaydi.
net/http va Gin middleware’lari farqi
net/http |
Gin | |
|---|---|---|
| Imzo | func(http.Handler) http.Handler |
func(*gin.Context) |
| Keyingisini chaqirish | next.ServeHTTP(w, r) |
c.Next() |
| To‘xtatish | nextni chaqirmaslik |
c.Abort() + return |
| Qiymat uzatish | context.WithValue |
c.Set / c.Get |
Middleware
const principalKey = "principal"
type Principal struct {
UserID int64
Role string
}
func AuthRequired(tokens *TokenService) gin.HandlerFunc {
return func(c *gin.Context) {
raw, ok := bearerToken(c.GetHeader("Authorization"))
if !ok {
unauthorized(c, "missing_token", "Authorization sarlavhasida Bearer token kerak")
return
}
claims, err := tokens.Parse(raw)
switch {
case errors.Is(err, jwt.ErrTokenExpired):
unauthorized(c, "token_expired", "token muddati tugagan")
return
case err != nil:
unauthorized(c, "invalid_token", "token yaroqsiz")
return
}
userID, err := strconv.ParseInt(claims.Subject, 10, 64)
if err != nil {
unauthorized(c, "invalid_token", "token yaroqsiz")
return
}
c.Set(principalKey, Principal{UserID: userID, Role: claims.Role})
c.Next()
}
}
func unauthorized(c *gin.Context, code, msg string) {
c.Header("WWW-Authenticate", `Bearer error="invalid_token"`)
c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{
"error": gin.H{"code": code, "message": msg},
})
}
AuthRequiredmiddleware qaytaradigan funksiya.TokenServiceunga parametr sifatida beriladi, global o‘zgaruvchi ishlatilmaydi.c.AbortWithStatusJSONjavobni yozadi va zanjirni to‘xtatadi. Undan keyingireturnjoriy funksiyani tugatadi.- Foydalanuvchi
c.Setbilan saqlanadi.
Foydalanuvchini olish
c.Get any qaytaradi. Har bir handlerda tur tekshiruvini takrorlamaslik uchun yordamchi funksiya yozamiz:
func CurrentPrincipal(c *gin.Context) (Principal, bool) {
v, ok := c.Get(principalKey)
if !ok {
return Principal{}, false
}
p, ok := v.(Principal)
return p, ok
}
Kalit oddiy satr bo‘lgani uchun uni konstanta sifatida e’lon qildik. Shunda turli joylarda "principal" va "Principal" kabi har xil yozilib qolishining oldi olinadi.
Service qatlamiga uzatish
Service gin.Contextni bilmasligi kerak. Unga faqat kerakli qiymatlar va so‘rov contexti uzatiladi:
func (h *Handler) createBook(c *gin.Context) {
p, _ := CurrentPrincipal(c)
book, err := h.books.Create(c.Request.Context(), p.UserID, req.Title)
// ...
}
Principal’ni context.Context orqali ham uzatish kerak bo‘lsa, middleware’da so‘rov contextini ham yangilash mumkin:
Bu usul net/http uchun yozilgan kod bilan birga ishlatilganda qulay.
Guruhlarni himoyalash
r := gin.New()
r.Use(gin.Logger(), gin.Recovery())
r.POST("/auth/login", h.login)
r.GET("/books", h.listBooks)
authed := r.Group("")
authed.Use(AuthRequired(tokens))
{
authed.GET("/me", h.me)
authed.POST("/books", RequirePermission("books:create"), h.createBook)
admin := authed.Group("/admin", RequireRole("admin"))
admin.GET("/stats", h.stats)
}
r.Group("")— prefikssiz guruh. U faqat middleware’larni birlashtiradi.RequirePermission("books:create")faqat bitta marshrutga qo‘shildi. Gin’da marshrutga istalgancha handler berish mumkin:r.POST(yo‘l, mw1, mw2, handler).authed.Group("/admin", RequireRole("admin"))— guruh yaratilayotganda middleware darhol qo‘shildi.
Warning
Gin arxitekturasi darsida aytilganidek, Use marshrutlardan oldin chaqirilishi kerak. authed.Use(...) qatori authed.GET(...) dan keyin yozilsa, /me endpointi himoyasiz qoladi.
Ruxsat middleware’lari
func RequireRole(roles ...string) gin.HandlerFunc {
return func(c *gin.Context) {
p, ok := CurrentPrincipal(c)
if !ok {
c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"error": gin.H{"code": "unauthorized"}})
return
}
if !slices.Contains(roles, p.Role) {
c.AbortWithStatusJSON(http.StatusForbidden, gin.H{"error": gin.H{"code": "forbidden"}})
return
}
c.Next()
}
}
RequirePermission ham xuddi shunday yoziladi. Faqat rol ro‘yxati o‘rniga RBAC darsidagi Can(role, perm) funksiyasi chaqiriladi.
To‘liq misol
| main.go | |
|---|---|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 | |
Info
Misolda diqqat faqat middleware’ga qaratilishi uchun login soddalashtirilgan: parol tekshirilmaydi. Parolni to‘g‘ri tekshirish Login / Signup endpointlari darsida ko‘rsatilgan.
Sinab ko‘ramiz:
U=http://localhost:8080
AZIZA=$(curl -s -X POST $U/auth/login -d '{"username":"aziza"}' | jq -r .access_token)
ADMIN=$(curl -s -X POST $U/auth/login -d '{"username":"admin"}' | jq -r .access_token)
curl -s $U/me
curl -s $U/me -H "Authorization: Bearer $AZIZA"
curl -s $U/admin/stats -H "Authorization: Bearer $AZIZA"
curl -s $U/admin/stats -H "Authorization: Bearer $ADMIN"
{"error":{"code":"missing_token","message":"Authorization sarlavhasida Bearer token kerak"}}
{"data":{"id":1,"role":"author"}}
{"error":{"code":"forbidden","message":"bu bo‘lim uchun ruxsat yo‘q"}}
{"data":{"users":2}}
Tayyor kutubxonalar haqida
Gin uchun tayyor JWT middleware kutubxonalari ham bor. Masalan, github.com/appleboy/gin-jwt. Ular login, tokenni yangilash va chiqish endpointlarini ham tayyor holda beradi.
Tayyor kutubxona tanlaganda quyidagilarni tekshiring:
- u qaysi JWT kutubxonasidan foydalanadi va u yangilanib turadimi;
- algoritm qat’iy tekshiriladimi, ya’ni
alg: nonehujumidan himoyalanganmi; - xato javoblari formatini o‘zgartirish mumkinmi.
Bu darsdagi middleware atigi bir necha o‘n qatordan iborat. Uni o‘zingiz yozsangiz, har bir qator sizning nazoratingizda bo‘ladi va tashqi bog‘liqlik kamayadi.
Xulosa
- Gin middleware’i
func(*gin.Context)ko‘rinishida bo‘ladi vac.Next(),c.Abort()bilan boshqariladi. - Tokenni tekshirish mantiqi
net/httpversiyasi bilan bir xil.TokenServiceframework’dan mustaqil. - Foydalanuvchi
c.Setbilan saqlanadi va yordamchi funksiya orqali o‘qiladi. - Himoyalangan marshrutlar guruhga yig‘iladi.
Usemarshrutlardan oldin chaqiriladi. - Ruxsat middleware’lari guruhga yoki alohida marshrutga qo‘shiladi.
- Service qatlamiga
gin.Contextemas,c.Request.Context()va oddiy qiymatlar uzatiladi.
Keyingi darsda Gin’da xatolarni markazlashgan holda qayta ishlashni ko‘ramiz.