Skip to main content

Casbin权限控制框架

Casbin权限控制框架

简介

Casbin + JWT 是 gin 生态身份认证的最佳实践。

简单来说:这个用户,用这个方法,访问这个接口,允许吗?

sub(谁) + obj(访问哪个接口) + act(用什么方法)

项目结构

PS C:\Users\kk\Desktop\CasbinDemo> tree /F
文件夹 PATH 列表
卷序列号为 AA59-C1A0
C:.
casbin.db
go.mod
go.sum
main.go
model.conf

main.go

package main

import (
"fmt"
"time"

"github.com/gin-gonic/gin"
"github.com/golang-jwt/jwt/v5"

"github.com/casbin/casbin/v3"
gormadapter "github.com/casbin/gorm-adapter/v3"

"gorm.io/driver/sqlite"
"gorm.io/gorm"
)

// ================= JWT =================

var jwtKey = []byte("secret-key")

type Claims struct {
Username string `json:"username"`
Role string `json:"role"`
jwt.RegisteredClaims
}

func GenerateToken(username, role string) (string, error) {
claims := Claims{
Username: username,
Role: role,
RegisteredClaims: jwt.RegisteredClaims{
ExpiresAt: jwt.NewNumericDate(time.Now().Add(time.Hour)),
},
}

token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
return token.SignedString(jwtKey)
}

func ParseToken(tokenStr string) (*Claims, error) {
token, err := jwt.ParseWithClaims(tokenStr, &Claims{}, func(t *jwt.Token) (interface{}, error) {
return jwtKey, nil
})
if err != nil {
return nil, err
}

if claims, ok := token.Claims.(*Claims); ok && token.Valid {
return claims, nil
}
return nil, fmt.Errorf("invalid token")
}

// ================= Casbin =================

func InitCasbin() *casbin.Enforcer {
db, err := gorm.Open(sqlite.Open("casbin.db"), &gorm.Config{})
if err != nil {
panic(err)
}

adapter, err := gormadapter.NewAdapterByDB(db)
if err != nil {
panic(err)
}

e, err := casbin.NewEnforcer("model.conf", adapter)
if err != nil {
panic(err)
}

// 加载数据库里的策略
e.LoadPolicy()

// 如果第一次运行没有数据,就初始化
_, _ = e.AddPolicy("admin", "/data1", "GET")
_, _ = e.AddPolicy("admin", "/data2", "GET")
_, _ = e.AddPolicy("user", "/data2", "GET")

_ = e.SavePolicy()

return e
}

// ================= Middleware =================

func AuthMiddleware(e *casbin.Enforcer) gin.HandlerFunc {
return func(c *gin.Context) {

tokenStr := c.GetHeader("Authorization")
if tokenStr == "" {
c.JSON(401, gin.H{"msg": "no token"})
c.Abort()
return
}

claims, err := ParseToken(tokenStr)
if err != nil {
c.JSON(401, gin.H{"msg": "invalid token"})
c.Abort()
return
}

sub := claims.Role
obj := c.Request.URL.Path
act := c.Request.Method

ok, err := e.Enforce(sub, obj, act)
if err != nil {
c.JSON(500, gin.H{"msg": err.Error()})
c.Abort()
return
}

if !ok {
c.JSON(403, gin.H{"msg": "forbidden"})
c.Abort()
return
}

c.Set("user", claims.Username)
c.Next()
}
}

// ================= main =================

func main() {

e := InitCasbin()

r := gin.Default()

// 登录
r.POST("/login", func(c *gin.Context) {

var req struct {
User string `json:"user"`
Role string `json:"role"`
}

if err := c.BindJSON(&req); err != nil {
c.JSON(400, gin.H{"msg": "bad request"})
return
}

token, _ := GenerateToken(req.User, req.Role)

c.JSON(200, gin.H{
"token": token,
})
})

// 需要鉴权的路由
auth := r.Group("/")
auth.Use(AuthMiddleware(e))

auth.GET("/data1", func(c *gin.Context) {
c.JSON(200, gin.H{"msg": "data1 only admin"})
})

auth.GET("/data2", func(c *gin.Context) {
c.JSON(200, gin.H{"msg": "data2 admin + user"})
})

r.Run(":8080")
}

model.conf

[request_definition]
r = sub, obj, act

[policy_definition]
p = sub, obj, act

[policy_effect]
e = some(where (p.eft == allow))

[matchers]
m = r.sub == p.sub && r.obj == p.obj && r.act == p.act