RBAC
Importáld az rbac alcsomagot, hogy szerepeket és jogosultságokat ellenőrizz az autentikált identity fölött:
import "github.com/gp-system/auth/rbac"
Az rbac az auth modul szerep- és jogosultság-ellenőrző alcsomagja: szerep- és jogosultság-ellenőrzéseket ad egy autentikált Identity fölött, ami a request contextben utazik. Nem foglal állást arról, honnan jönnek a szerepek: az auth.Identify (vagy bármi más) tölti be az Identity-t, az rbac csak ellenőriz: adatbázis-sémát, user-táblát nem feltételez, és sosem ír választ.
A csomag három szintet fed le: id.HasRole("admin") / id.HasPermission("orders.view") a legegyszerűbb bool-ellenőrzésekhez, rbac.CheckRole(id, "admin") / rbac.CheckPermission(id, "orders.view"), ami bool helyett ErrUnauthenticated/ErrForbidden-t ad vissza (akkor hasznos, ha külön nil-ellenőrzés nélkül meg kell különböztetned a "nincs identity"-t a "rossz szerep"-től), és a gpsystem kit server.RequireRole("admin") / server.RequirePermission("orders.view")-ja a route-szintű net/http middleware-védelemhez, ami ezt a két sentinelt Problemként rendereli. Egy kit nélküli, csak CheckRole-ra épülő, kézzel írt middleware-hez lásd az Önálló használat oldalt.
Az Identity
Az Identity a hívó, ahogy az authorizációs döntések és az üzleti logika látja: ez a kontraktus az autentikáció és az authorizáció között:
type Identity struct {
Subject string // a user azonosítója (a JWT sub claimje)
Username string
Roles []string
Permissions []string
Extra map[string]any // a standard mezőkön túli claim-adat (pl. tenant)
}
Két lekérdező metódusa van, mindkettő any-of szemantikával: több argumentumból egy találat is elég.
id.HasRole("admin", "editor") // igaz, ha bármelyik szerep megvan
id.HasPermission("orders.view") // igaz, ha a permission megvan
Mindkettő biztonságosan hívható nil identity-n is (ilyenkor false-t ad); ez illeszkedik ahhoz, hogy a FromContext nil-t ad vissza, ha a kérés nem ment át auth middleware-en. Így a guard-kódodban nem kell nil-ellenőrzést írnod:
if rbac.FromContext(ctx).HasRole("admin") { // nil-en is működik, false-t ad
// ...
}
WithIdentity és FromContext
Az Identity sima context.Context-ben utazik:
ctx = rbac.WithIdentity(ctx, id) // az auth middleware hívja
id := rbac.FromContext(ctx) // *rbac.Identity, vagy nil auth nélkül
Az Identity, a WithIdentity és a FromContext framework-független: se net/http-importja, se router-importja nincs. Ezért ugyanez a kontraktus használható a HTTP-rétegen kívül is: service-ekben, listenerekben és jobokban, ha a contextbe identity került.
A shopban például a rendeléslistázás service-szinten szűkül a hívóra: az admin mindent lát, a vásárló csak a sajátját.
// internal/modules/shop/surfaces/api/service/orders.go
func (s *Service) ListOrders(ctx context.Context, cursor string) ([]Order, error) {
id := rbac.FromContext(ctx)
if id.HasRole("admin") {
return s.orders.ListAll(ctx, cursor)
}
return s.orders.ListByUser(ctx, id.Subject, cursor)
}
Ez adat-szűkítés (mit lásson), nem hozzáférésdöntés (szabad-e egyáltalán). A kérésenkénti engedélyezés, azaz hogy „megnézheti-e ezt a konkrét rendelést", a policy-k dolga.
Route-guardok
A kit server.RequireRole / server.RequirePermission middleware-ei egy egész route-csoportot védenek. Feltételezik, hogy előttük már futott a server.AuthMiddleware; ők csak a contextbeli Identity-t vizsgálják, az rbac.CheckRole/CheckPermission-en keresztül, és az eredményt Problemként rendereli.
router.Route("/admin/shop", func(r chi.Router) {
r.Use(
server.AuthMiddleware(deps.Auth), // Bearer → parse → Identity a contextbe
server.RequireRole("admin"), // identity nélkül 401, szerep nélkül 403
)
// ...
})
// permission-alapú változat, any-of szemantikával:
router.Route("/reports", func(r chi.Router) {
r.Use(server.AuthMiddleware(deps.Auth),
server.RequirePermission("reports.view", "reports.export"))
// ...
})
A válaszok RFC 9457 problem dokumentumok:
| Helyzet | Eredmény |
|---|---|
nincs Identity a contextben (nem futott auth) | 401 authentication required |
| van identity, de egyik felsorolt szerep/permission sincs meg | 403 insufficient privileges |
| bármelyik felsorolt szerep/permission megvan (any-of) | átengedés |
Ez alatt a server.RequireRole/RequirePermission közvetlenül az rbac.CheckRole/CheckPermission-t hívja, és az rbac.ErrUnauthenticated/ErrForbidden-t képezi le erre a két státuszra; egy kit nélküli hívó ugyanezt a leképzést kézzel végzi el, ahogy az Önálló használat oldal mutatja.
A kittel
A shopban a teljes admin surface (termék CRUD, képfeltöltés) a fenti párossal van védve: az add surface által generált RegisterAdmin függvénybe kerül a middleware, így a védelem surface-szintű, és a testvér api surface-t nem érinti. A pontos bekötést (és a deps.Auth mező felvételét) az autentikáció oldal mutatja; a surface-per-függvény szerkezetről az add surface szól.
Ugyanaz az Identity a HTTP kérés/válasz-cikluson kívül is felbukkan: a realtime gateway minden WebSocket-kapcsolat JWT-jét Identity-vé oldja fel, és a topicra regisztrált Allow/AllowPrefix callbackek ellen ellenőrzi, így egy kapcsolat csak olyan csatornákra iratkozhat fel, amelyekre a hívó jogosult.
Finomabb szemcséhez ugyanezek a guardok route-csoport helyett kisebb csoportra is tehetők, de ha a döntés már nem „van-e ilyen szerepe", hanem „az övé-e ez az erőforrás", az nem RBAC: arra a policy-réteg való.
Honnan jönnek a szerepek és a permissionök
A kit alapfelállásában a JWT claimjeiből: a login végpont a DefaultClaims-be írja a user szerepeit és permissionjeit, az auth middleware pedig ezekből tölti az Identity mezőit. A teljes lánc az autentikáció oldalon. Mivel az rbac csak az Identity-t látja, a forrás lecserélhető: saját claims-típus (Identify/server.AuthMiddlewareFor), API-kulcsból épített identity vagy teszt-fixture ugyanúgy működik.
rbac.WithIdentity-vel közvetlenül a contextbe teheted a kívánt identityt, és a service-réteg úgy viselkedik, mintha a middleware töltötte volna be.ctx := rbac.WithIdentity(t.Context(), &rbac.Identity{
Subject: "u-42", Roles: []string{"admin"},
})
A szerep azt mondja meg, a hívó általában mit tehet. Ha a döntéshez a konkrét kérés is kell (tulajdonos-e, egy tenantban vannak-e, jó állapotban van-e a rekord), lépj tovább a policy-kra.
Használt patternek
- Context-injektált identity (
WithIdentity/FromContext, unexported kulccsal, nil-safe metódusok): Design patternek. - Magasabb-rendű authorizációs guard (
requireFn, amit aRequireRoleés aRequirePermissionis megoszt): Design patternek.