gin学习
创始人
2024-04-24 23:14:00
0

文章目录

      • 零、知识补充
        • GOPROXY地址
      • 一、准备工作
        • 1、安装gin包(mod模式)
        • 2、文档
        • 3、测试 hello gin
      • 二、GET POST PUT DELETE请求的使用
        • 1、修改端口号
        • 2、GET 查
        • 3、POST 增
        • 4、DELETE 删
        • 5、PUT 改
        • 6、如何取出参数
          • 6.1、GET
          • 6.2、POST DELETE PUT
          • 6.3、URI
      • 三、Bind模式获取参数和表单验证
        • 3.1、bind模式如何使用
        • 3.2、MustBind
        • 3.3、ShouldBind
      • 四、gin对于文件的接收和返回
        • 1、读取文件
        • 2、本地写文件
        • 3、给前端返回文件
      • 五、gin中间件和路由分组
        • 1、什么是分组(如何创建)
        • 2、为什么要分组
        • 3、什么是中间件
        • 4、如何使用中间件
        • 5、如何创建中间件
      • 六、gin日志和日志格式
        • 1、为什么要使用日志
        • 2、gin自带日志写入中间件
        • 3、第三方日志中工具
        • 4、日志切割
      • 七、gorm初探
        • 1、什么是orm
        • 2、orm如何链接数据库
        • 3、自动化创建数据库表
        • 4、最最简单的增删改查
      • 八、结构体的创建技巧和结合gin使用
        • 1、tag设置
        • 2、自定义表名(动态表名)
        • 3、结构体声明 1对1 1对多 多对多
        • 4、使用gin接受参数并且经过处理入库或者返回
      • 九、jwt-go
        • 1、什么是jwt
        • 2、如何创建一个JWT
          • 两种常用 Claims的实现方式
            • 匿名函数实现接口
            • map形式
          • 创建一个Token
        • 3、如何解析一个JWT
      • 十、casbin模型
        • 1、casbin模型基础
          • 1.1、PERM 元模型
          • 1.2、role_definition 角色域
        • 2、实战模型
          • 2.1、RBAC
          • 2.2、RBAC with domains/tenants
        • 3、实战策略
          • 3.1、角色为基础的
          • 3.2、带域的
      • 十一、使用casbin
        • 1、本地文件模式初体验
        • 2、使用数据库存储policy
        • 3、对policy进行增删改查
        • 4、自定义比较函数


golang 1010工作室
视频地址:https://www.bilibili.com/video/BV12i4y1x7AG/?spm_id_from=333.788&vd_source=eba330e2ab2e59ae2e4ecace161e0983

零、知识补充

URI和URL的概念和区别 - 掘金 (juejin.cn)

URL是统一资源定位符(像人的地址),URI是统一资源标识符(像人的身份证)

GOPROXY地址

GOPROXY=https://goproxy.cn,direct

一、准备工作

1、安装gin包(mod模式)

  • go 版本 >= v1.11
  • 翻墙的东西可能无法下载 GOPROXY=https://goproxy.io
  • GO111MODULE = auto
  • go mod init 你自己想叫的项目名字 包名字 或者github项目地址 如果你开发的是一个共用包的话 最好是github地址

2、文档

https://www.kancloud.cn/shuangdeyu/gin_book/949411

golang找不到对应的包。go get github.com/gin-gonic/gin

import "github.com/gin-gonic/gin"func main() {r := gin.Default() //携带基础中间件启动路由r.GET("/ping", func(c *gin.Context) {c.JSON(200, gin.H{"message": "pong",})})r.Run() // listen and serve on 0.0.0.0:8080
}

找到一个最适合自己的中文文档

3、测试 hello gin

localhost:8080/ping

二、GET POST PUT DELETE请求的使用

1、修改端口号

Run(":1010")

2、GET 查

参数挂在url中 uri中传参

r := gin.Default() //携带基础中间件    启动路由
r.GET("/path/:id", func(c *gin.Context) {//c 上下文id := c.Param("id")//user和pwd 地址栏后面的,用query传参.默认传参,如果不存在给一个默认值user := c.DefaultQuery("user", "qimiao")pwd := c.Query("pwd")c.JSON(200, gin.H{"id":   id,"user": user,"pwd":  pwd,})
})
r.Run()

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-5KpIEPws-1671192558369)(gin视频学习.assets/image-20221212113843442.png)]

3、POST 增

参数在form body中 或者uri

r.POST("/path", func(c *gin.Context) {//从form表单中取数据user := c.DefaultPostForm("user", "qimiao")pwd := c.PostForm("pwd")c.JSON(200, gin.H{"user": user,"pwd":  pwd,})
})

image-20221212114319235

4、DELETE 删

一般情况为uri 同样也可以用body

r.DELETE("/path/:id", func(c *gin.Context) {id := c.Param("id")c.JSON(200, gin.H{"id": id,})
})

image-20221212114744581

5、PUT 改

参数在form body 或者uri

r.PUT("/path", func(c *gin.Context) {//从form表单中取数据user := c.DefaultPostForm("user", "qimiao")pwd := c.PostForm("pwd")c.JSON(200, gin.H{"user": user,"pwd":  pwd,})
})

image-20221212114529155

6、如何取出参数

6.1、GET

一般情况下为 地址栏的query

   //user和pwd 地址栏后面的,用query传参.默认传参,如果不存在给一个默认值user := c.DefaultQuery("user", "qimiao")pwd := c.Query("pwd")
6.2、POST DELETE PUT

一般情况下 为FORM参数

   //从form表单中取数据user := c.DefaultPostForm("user", "qimiao")pwd := c.PostForm("pwd")
6.3、URI

地址栏定义占位符 通过占位符取参数

三、Bind模式获取参数和表单验证

3.1、bind模式如何使用

bind模式一定要设置tag

如果一个字段用binding:"required"修饰,并且在绑定时该字段的值为空,那么将返回一个错误。json uri form

type PostParams struct {Name string `json:"name" uri:"name" form:"name"`Sex bool	`json:"sex"  uri:"sex"  form:"sex"`
}func main() {r := gin.Default()//r.POST("/restBind", func(c *gin.Context) {//r.POST("/restBind/:name/:sex", func(c *gin.Context) {r.POST("/restBind", func(c *gin.Context) {var p PostParams//err := c.ShouldBindJSON(&p)//err := c.ShouldBindUri(&p)   //restBind/你好/trueerr := c.ShouldBindQuery(&p)   //restBind?name=qimiao&sex=trueif err != nil {c.JSON(400, gin.H{"mes":  "报错了","data": gin.H{},})} else {c.JSON(200, gin.H{"mes":  "成功","data": p,})}})r.Run(":8080")
}

3.2、MustBind

3.3、ShouldBind

  • 表单验证
binding:"required"  //传入参数的不能为空
  • 自定义验证

在这里插入图片描述

type PostParams struct {//		ShouldBindJSON、ShouldBindUri、ShouldBindQueryName string `json:"name" uri:"name" form:"name" `Sex  bool   `json:"sex" uri:"sex" form:"sex" `Age  int    `json:"age" uri:"sex" form:"sex"  binding:"required,mustBig"`
}/*
{"name":"奇妙","sex":true,"age":17
}
报错信息:Key: 'PostParams.Age' Error:Field validation for 'Age' failed on the 'mustBig' tag
*/func mustBig(fl validator.FieldLevel) bool {if fl.Field().Interface().(int) <= 18 {return false}return true
}func main(){r := gin.Default()if v, ok := binding.Validator.Engine().(*validator.Validate); ok {v.RegisterValidation("mustBig", mustBig)}//r.POST("/restBind", func(c *gin.Context) {//r.POST("/restBind/:name/:sex", func(c *gin.Context) {r.POST("/restBind", func(c *gin.Context) {var p PostParamserr := c.ShouldBindJSON(&p)//err := c.ShouldBindUri(&p)   //restBind/你好/true//err := c.ShouldBindQuery(&p) //restBind?name=qimiao&sex=truefmt.Println(err)if err != nil {c.JSON(400, gin.H{"mes":  "报错了","data": gin.H{},})} else {c.JSON(200, gin.H{"mes":  "成功","data": p,})}})r.Run(":8080")
}

四、gin对于文件的接收和返回

1、读取文件

读取到的文件就可以进行文件的操作

c.FormFile("前端放到file里面的name")

2、本地写文件

  • 原生:这里使用os.create方法来写

  • gin封装的 c.SaveUploadedFile(file, dst)

    r := gin.Default()r.POST("/testUpload", func(c *gin.Context) {file, _ := c.FormFile("file")name := c.PostForm("name")//c.SaveUploadedFile(file, "./"+file.Filename)  //等于以下手写封装功能in, _ := file.Open()defer in.Close()out, _ := os.Create("./" + file.Filename)io.Copy(out, in)defer out.Close()c.JSON(200, gin.H{"mes": file,"name": name,})})
    r.Run(":8080")
    

在这里插入图片描述

多文件上传

上传文件 · Gin中文文档 · 看云 (kancloud.cn)

3、给前端返回文件

c.Writer.Header().Add("Content-Disposition", fmt.Sprintf("attachment; filename=%s", "文件名")) //fmt.Sprintf("attachment; filename=%s", filename)对下载的文件重命名
r := gin.Default()r.POST("/testUpload", func(c *gin.Context) {file, _ := c.FormFile("file")//name := c.PostForm("name")//c.SaveUploadedFile(file, "./"+file.Filename)  //以下手写封装功能in, _ := file.Open()defer in.Close()out, _ := os.Create("./" + file.Filename)defer out.Close()io.Copy(out, in)//文件写回前端c.Writer.Header().Add("Content-Disposition", fmt.Sprintf("attachment; filename=%s", file.Filename))c.File("./" + file.Filename)   //保存文件到前端})

在这里插入图片描述

五、gin中间件和路由分组

1、什么是分组(如何创建)

  • 对 router 创建 Group就是分组
    同一分组会拥有同一前缀和同一中间件

  • 写法:

    router:= gin.Default()   
    v1 := router.Group("/v1") {
    v1.POST("/login", loginEndpoint)
    v1.POST("/submit", submitEndpoint)
    v1.POST("/read", readEndpoint) }
    

案例

func main() {r := gin.Default()v1 := r.Group("v1")v1.GET("/test", func(c *gin.Context) {fmt.Println("我在分组v1里面")c.JSON(200, gin.H{"success": true,})})v1.GET("/test2", func(c *gin.Context) {fmt.Println("我在分组v1里面")c.JSON(200, gin.H{"success": true,})})r.Run(":8080")
// 访问:http://localhost:8080/v1/test
}

2、为什么要分组

  • 路由结构更加清晰
  • 更加方便管理路由

3、什么是中间件

在请求到达路由的方法的前和后进行的一系列操作

4、如何使用中间件

在路由器(路由组)上进行use操作 后面传入中间件函数即可

5、如何创建中间件

有点类似洋葱,从外往里走,走完在从里走出去。属于洋葱中间件.

func funcname() gin.HandlerFunc {return func(c *gin.Context) {fmt.Println("before request")c.Next() fmt.Println("after request")}
}

案例

//创建中间件
func middle() gin.HandlerFunc {return func(c *gin.Context) {fmt.Println("我在方法前,我是1")c.Next() //是否往下面走fmt.Println("我在方法后,我是1")}
}//创建中间件
func middle2() gin.HandlerFunc {return func(c *gin.Context) {fmt.Println("我在方法前,我是2")c.Next() //是否往下面走,走到2后next发现没有中间件了,走方法内部,然后走2,走完走1fmt.Println("我在方法后,我是2")}
}func main() {r := gin.Default()v1 := r.Group("v1").Use(middle(), middle2())//等效于middle(). middle2()v1.GET("/test", func(c *gin.Context) {fmt.Println("我在分组v1里面")c.JSON(200, gin.H{"success": true,})})v1.GET("/test2", func(c *gin.Context) {fmt.Println("我在分组v1里面")c.JSON(200, gin.H{"success": true,})})r.Run(":8080")//	访问:http://localhost:8080/v1/test
}
我在方法前,我是1
我在方法前,我是2
我在分组v1里面
我在方法后,我是2
我在方法后,我是1

六、gin日志和日志格式

1、为什么要使用日志

  • 记录参数信息
  • 猜测用户行为
  • 复现系统bug并修复

2、gin自带日志写入中间件

耦合度较高,自定义起来比较麻烦

3、第三方日志中工具

  • go-logging
  • logrus

4、日志切割

  • 自行根据时间在写入时判断进行切割日志
  • 借助成品的日志包:go-file-rotatelogs file-rotatelogs

七、gorm初探

1、什么是orm

  • 一种数据库操作辅助工具
  • 在我们go的结构体和数据库之间产生映射,让我们对数据库的关系,表的内容,直观得体现在结构体上。
  • 使用结构体即可完成增删改查操作

2、orm如何链接数据库

  • 导入 gorm

  • 导入mysql驱动器

  • 使用open链接 得到 数据库操作对象(以mysql为例)

 db, err := gorm.Open("mysql", "user:password@/dbname?charset=utf8&parseTime=True&loc=Local")defer db.Close()

3、自动化创建数据库表

gorm支持自动迁移模式 使用 AutoMigrate 方法来帮我们自动化创建数据库表

 // 自动迁移模式
db.AutoMigrate(&Product{})

4、最最简单的增删改查

  • 增:Create (跟结构体指针地址)

  • 删:Delete (跟结构体指针地址)或者条件 会根据主键自动去查询单条或者根据条件删除多条

  • 改:Update 更新单一数据 还有 Updates 更新数据中指定内容 Save更新所有内容

  • 查:First (跟结构体示例指针地址,查符合条件的第一个)

    	var user Userinfodb.First(&user, "name=?", "辉**")fmt.Println(user)
    
  • 查: Find (跟结构体切片指针地址,查符合条件的所有)

    	var user []Userinfodb.Find(&user)  //不跟条件,查所有fmt.Println(user)
    

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-XxMhNfIR-1671195882699)(gin视频学习.assets/image-20221213145913545.png)]

  • 条件:Where Or 填写简单的sql查询语句执行得到model

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-0meHtcFY-1671195882700)(gin视频学习.assets/image-20221213145937017.png)]

  • 模型:Model

import ("gorm.io/driver/mysql""gorm.io/gorm"
)type Userinfo struct {Id     uintName   stringGender stringHobby  stringgorm.Model
}func main() {// 连接数据库dsn := "root:123456@tcp(127.0.0.1:3306)/db2?charset=utf8mb4&parseTime=True&loc=Local" //是否格式化时间:是,时区本地db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})if err != nil {panic(err)}//自动迁移db.AutoMigrate(&Userinfo{})//增/*u1 := Userinfo{Name:   "亚**",Gender: "男",Hobby:  "编程",}db.Create(&u1)*//*//查var user []Userinfodb.Where("id<=?", 2).Find(&user)fmt.Println(user)//更新一条db.Where("id=?", 2).First(&Userinfo{}).Update("gender", "男")*//*//更新多条db.Where("id in (?)", []int{1, 2}).Find(&[]Userinfo{}).Updates(map[string]interface{}{"Name":   "六六","Gender": "男",})*//*db.Where("id in (?)", []int{1, 3}).Find(&[]Userinfo{}).Updates(Userinfo{Name:   "七七",Gender: "女",})*///db.Where("id=?", 1).Delete(&Userinfo{})  //软删除db.Where("id in (?)", []int{1, 2}).Unscoped().Delete(&Userinfo{}) //硬删除
}

八、结构体的创建技巧和结合gin使用

1、tag设置

模型定义 | GORM - The fantastic ORM library for Golang, aims to be developer friendly.

  • 设置主键:gorm:“primaryKey”
  • 自定义字段名字:column:user_id
  • 忽略:“-” (以后gorm跟这个字段就没有什么关系了)
  • 指定数据类型 type:varchar(100);
  • 非空 not null
  • 创建索引:index
  • 设置外键 ForeignKey
  • 关联外键 AssociationForeignKey
  • 多对多 many2many:表名;

2、自定义表名(动态表名)

type User struct {gorm.ModelName string `gorm:"primaryKey;column:user_name;type:varchar(100)"`
}
//例子一
func (User) TableName() string {return "qm_users"
}
//例子二
func (u User) TableName() string {if u.Role == "admin" {return "admin_users"} else {return "qm_users"}
} 

3、结构体声明 1对1 1对多 多对多

  • 多对多使用many2many关键字

    手写一对一、一对多、多对多关系

    //班级里有多个学生(一对多) --  学生属于classId班级(多对一) -- 一个学生有一个idCard(一对一) -- 一个学生多个老师,多个老师对一个学生(多对多)type Class struct {gorm.ModelClassName stringStudents  []Student //班级里有多个学生
    }type Student struct {gorm.ModelStudentName string// 学生属于classId班级ClassID uint//一个学生一个学生卡IdCard IdCard//多对多   学生有多个老师,并且知道老师的idTeachers []Teacher `gorm:"many2many:Student_Teachers"`//TeacherID uint}type IdCard struct {gorm.ModelStudentID uintNum       int
    }type Teacher struct {gorm.ModelTeacherName string//老师有多个学生,老师知道学生的id//StudentID uintStudents []Student `gorm:"many2many:Student_Teachers"`
    }func main() {dsn := "root:123456@tcp(127.0.0.1:3306)/db2?charset=utf8mb4&parseTime=True&loc=Local"db, _ := gorm.Open(mysql.Open(dsn), &gorm.Config{})db.AutoMigrate(&Teacher{}, &Class{}, &Student{}, &IdCard{})i := IdCard{Num: 123456}t := Teacher{TeacherName: "老师夫",//Students: []Student{s},}s := Student{StudentName: "qm",IdCard:      i,Teachers:    []Teacher{t},}c := Class{ClassName: "奇妙的班级",Students:  []Student{s},}_ = db.Create(&c).Error //把班级创建,班级把学生创建,学生把学生卡创建,学生把老师创建//_ = db.Create(&t).Error
    }
    
  • 分页查询使用 Count记录总数 使用Limit 和 Offset 指定记录位置

  • 预加载 Preload(可以把嵌套结构体数据也查出来)

    db.Preload("Teachers").Preload("IdCard").Where("id=?", id).First(&student)
    

    嵌套预加载

    db.Preload("Students").Preload("Students.IdCard").Preload("Students.Teachers").Where("id=?", id).First(&class)
    

在这里插入图片描述

4、使用gin接受参数并且经过处理入库或者返回

import ("github.com/gin-gonic/gin""gorm.io/driver/mysql""gorm.io/gorm"
)//班级里有多个学生(一对多) --  学生属于classId班级(多对一) -- 一个学生有一个idCard(一对一) -- 一个学生多个老师,多个老师对一个学生(多对多)type Class struct {gorm.ModelClassName stringStudents  []Student //班级里有多个学生
}type Student struct {gorm.ModelStudentName string// 学生属于classId班级ClassID uint//一个学生一个学生卡IdCard IdCard//多对多   学生有多个老师,并且知道老师的idTeachers []Teacher `gorm:"many2many:Student_Teachers"`//TeacherID uint}type IdCard struct {gorm.ModelStudentID uintNum       int
}type Teacher struct {gorm.ModelTeacherName string//老师有多个学生,老师知道学生的id//StudentID uintStudents []Student `gorm:"many2many:Student_Teachers"`
}func main() {dsn := "root:123456@tcp(127.0.0.1:3306)/db2?charset=utf8mb4&parseTime=True&loc=Local"db, _ := gorm.Open(mysql.Open(dsn), &gorm.Config{})db.AutoMigrate(&Teacher{}, &Class{}, &Student{}, &IdCard{})r := gin.Default()r.POST("/student", func(c *gin.Context) {var student Student_ = c.BindJSON(&student)db.Create(&student)})r.GET("/student/:ID", func(c *gin.Context) {id := c.Param("ID")var student Studentdb.Preload("Teachers").Preload("IdCard").Where("id=?", id).First(&student)c.JSON(200, gin.H{"mes":  "成功","data": student,})})r.GET("/class/:ID", func(c *gin.Context) {id := c.Param("ID")var class Classdb.Preload("Students").Preload("Students.IdCard").Preload("Students.Teachers").Where("id=?", id).First(&class)c.JSON(200, gin.H{"mes":  "成功","data": class,})})r.Run(":8888")/*{"StudentName":"qm","ClassID":1,"IdCard":{"Num":555},"Teachers":[{"TeacherName":"老师1"},{"TeacherName":"老师2"}]}*/
}

九、jwt-go

1、什么是jwt

官网

  • 全称 JSON WEB TOKEN
  • 一种后台不做存储的前端身份验证的工具
  • 分为三部分 Header Claims Signature

2、如何创建一个JWT

通常使用 NewWithClaims
因为我们可以通过匿名结构体来实现 Claims接口 从而可以携带自己的参数

这边我把一个Claims结构体从源码中粘贴出来

type StandardClaims struct {
Audience string `json:"aud,omitempty"`
ExpiresAt int64 `json:"exp,omitempty"`
Id string `json:"jti,omitempty"`
IssuedAt int64 `json:"iat,omitempty"`
Issuer string `json:"iss,omitempty"`
NotBefore int64 `json:"nbf,omitempty"`
Subject string `json:"sub,omitempty"`
}

其中最重要的三个参数

  • ExpiresAt //过期时间
  • Issuer //签发人
  • NotBefore //开始生效时间
两种常用 Claims的实现方式
匿名函数实现接口
type MyClaims struct {MyType string `json:"myType"`jwt.StandardClaims `json:"standardClaims"`
}匿名函数实现接口

案例加密

type MyClaims struct {UserName string `json:"username"`jwt.StandardClaims
}func main() {mySigningKey := []byte("qimiao")c := MyClaims{UserName: "qimiao",StandardClaims: jwt.StandardClaims{NotBefore: time.Now().Unix() - 60, //base64解成秒  60sExpiresAt: time.Now().Unix() + 60*60*2,Issuer:    "qimiao",},}t := jwt.NewWithClaims(jwt.SigningMethodHS256, c)fmt.Println(t)s, e := t.SignedString(mySigningKey)  //把t用密钥k加密if e != nil {fmt.Printf("%s", e)}fmt.Println(s)
}
//  加密串			头						体
//&{ 0xc000114108 map[alg:HS256 typ:JWT] {qimiao { 1670931390  0 qimiao 1670938530 }}  false}
//eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6InFpbWlhbyIsImV4cCI6MTY3MDkzMTk5NCwiaXNzIjoicWltaWFvIiwibmJmIjoxNjcwOTM5MTM0fQ.fo8_JrIbrb3tT4Awg2nok2povhpKh_3YQ-NOYxyBwvA

案例解密解密

import ("fmt""github.com/dgrijalva/jwt-go""time"
)type MyClaims struct {UserName string `json:"username"`jwt.StandardClaims
}func main() {mySigningKey := []byte("qimiao")c := MyClaims{UserName: "qimiao",StandardClaims: jwt.StandardClaims{NotBefore: time.Now().Unix() - 60, //base64解成秒  60sExpiresAt: time.Now().Unix() + 60*60*2,Issuer:    "qimiao",},}t := jwt.NewWithClaims(jwt.SigningMethodHS256, c)s, e := t.SignedString(mySigningKey)if e != nil {fmt.Printf("%s", e)}fmt.Println(s)time.Sleep(2 * time.Second)token, err := jwt.ParseWithClaims(s, &MyClaims{}, func(token *jwt.Token) (interface{}, error) {return mySigningKey, nil})if err != nil {fmt.Printf("%s", err)return}//fmt.Println(token.Claims) //&{qimiao { 1670990981  0 qimiao 1670983721 }}fmt.Println(token.Claims.(*MyClaims).UserName)
}
map形式
jwt.MapClaims{"myType": "myType","nbf": time.Date(2015, 10, 10, 12, 0, 0, 0, time.UTC).Unix(),"exp": time.Date(2015, 10, 10, 12, 0, 0, 0, time.UTC).Unix(),
}map形式

案例

func main() {mySigningKey := []byte("qimiao")t := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{"exp":      time.Now().Unix() + 60,"iss":      "qimiao","nbf":      time.Now().Unix() - 5,"username": "my",})s, e := t.SignedString(mySigningKey)if e != nil {fmt.Printf("%s", e)}fmt.Println(s)time.Sleep(2 * time.Second)token, err := jwt.ParseWithClaims(s, &jwt.MapClaims{}, func(token *jwt.Token) (interface{}, error) {return mySigningKey, nil})if err != nil {fmt.Printf("%s", err)return}//fmt.Println(token.Claims) //&{qimiao { 1670990981  0 qimiao 1670983721 }}fmt.Println(token.Claims.(*jwt.MapClaims))
}
创建一个Token
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims) // 创建
ss,err := token.SignedString(mySigningKey) // 签发
HS256(对称加密)  RS256(非对称加密  需要一个结构体指针 三个属性{ECDSAKeyD,
ECDSAKeyX,
ECDSAKeyY},ES256)

3、如何解析一个JWT

token,err := jwt.ParseWithClaims(token,claims,func)
  • token:我们拿到的token字符串 (ss)
  • 我们用哪个claims结构体发的 这里就传入哪个结构体
  • func: 一个特殊的回调函数 需要固定接受 *Token类型指针 返回一个 i和一个err 此时的i就是我们的密钥

token是一个jwtToken类型的数据 我们需要的就是其中 的Claims

对Claims进行断言 然后进行取用即可

   token, err := jwt.ParseWithClaims(s, &MyClaims{}, func(token *jwt.Token) (interface{}, error) {return mySigningKey, nil})if err != nil {fmt.Printf("%s", err)return}//fmt.Println(token.Claims) //&{qimiao { 1670990981  0 qimiao 1670983721 }}fmt.Println(token.Claims.(*MyClaims).UserName)

十、casbin模型

Model 的语法 | Casbin

1、casbin模型基础

1.1、PERM 元模型

p策略(Policy )、e影响(Effect )、r请求( Request )、m匹配规则(Matchers )

Editor | Casbin

  • subject(sub 访问实体), object(obj 访问的资源)和 action(act 访问方法)eft(策略结果 一般为空 默认指定 allow) 还可以定义为 deny

  • Policy 策略 p={sub, obj, act, eft}

    • 策略一般存储到数据库 因为会有很多

    • [policy_definition]
      p = sub,obj,act
      
  • Matchers 匹配规则 Request和Policy的匹配规则。

    • m = r.sub == p.sub && r.act == p.act && r.obj == p.obj
      
    • r 请求 p 策略

    • 这时候会把 r 和 p按照上述描述进行匹配 从而返回匹配结果(eft)如果不定义 会返回 allow 如果定义过了 会返回我们定义过的那个结果

  • Effect 影响

    • 它决定我们是否可以放行
      • e = some(where(p.eft == allow)) 这种情况下 我们的一个 matchers匹配完成 得到了allow 那么这条请求将被放行
      • e = some(where(p.eft == allow)) && !some(where(p.eft == deny))
      • 这里的规则是定死的
  • Request 请求 r={sub, obj,act}

Request带着匹配规则(matchers)和Policy匹配,返回一个结果,再通过effect校验是否能校验通过

image-20221214200926229

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Pz5seg2J-1671199742219)(null)]

1.2、role_definition 角色域
  • g = _ , _ 表示以角色为基础(用户是哪个角色)
  • g=_, _, _ 表示以域为基础(多商户模式)(用户是哪个角色 属于哪个商户)

image-20221214201221714

在这里插入图片描述

image-20221214201928256

加完g——我的入参可以是alice也可以是data2_admin

2、实战模型

2.1、RBAC

以角色为基础

  • [request_definition]
    r = sub, obj, act
    
  • 人话:请求入参(实体,资源,方法)

  • [policy_definition]
    p = sub, obj, act
    
  • 人话:策略(实体,资源,方法)

    [role_definition]
    g = _, _ 
    
  • 人话:这个情况下 g写啥都行 毕竟match里面根本没有涉及到g 不过我们规范一点 按照角色权限 这里意思是g收两个参数 g=用户,角色

  • [policy_effect]
    e = some(where (p.eft == allow))
    
  • 人话 :看看经过下面那些个匹配规则后的返回值是否有一条等于里面那个 allow

  • [matchers]
    m = r.sub == p.sub && ParamsMatch(r.obj,p.obj) && r.act == p.act
    
  • 人话:进来的实体 资源 方法 能不能在 权限表(p)里面找到一个一模一样的

2.2、RBAC with domains/tenants
  • 多租户模型

    [request_definition]r = sub, dom, obj, act
    
  • 人话:入参(实体,域【商户】,资源,方法)

  • [policy_definition]p = sub, dom, obj, act
    
  • 人话:权限模型(实体,域【商户】,资源,方法)

  • [role_definition]g = _, _, _
    
  • 半句人话:域匹配规则 后面g会说 这里意思是 g收三个参数(属于哪个用户,用户属于哪个角色,角色属于哪个商户)

  • [policy_effect]e = some(where (p.eft == allow))
    
  • 人话 :看看经过下面那些个匹配规则后的返回值是否等于里面那个 allow

  • [matchers]m = g(r.sub, p.sub, r.dom) && r.dom == p.dom && r.obj == p.obj && r.act == p.act
    
  • 先列出来一个权限定义里面的东西

  • g,qm,teacher,classOne
    
  • 然后用g做一些解析动作

  • 一般我们会收到前端的入参 大概长这样 “qm”,“classOne”,“/base/api”,“get”

  • 上面我们定义的g就是我们需要的模型 经过g以后 g已经把我们的入参 qm 解析成了 r.sub 为 teacher r.rom 为 classOne 先从这个商户找一下角色 找到就过了 然后用 dom 去匹配然后用一个类似于这样的请求 去 策略里面找

  • teacher,classOne ,/api/base,get
    

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-szCblPmh-1671199741730)(null)]

在这里插入图片描述

先匹配,然后根据p获得eft,然后去policy获得策略效果

3、实战策略

3.1、角色为基础的
  • p admin,/api/base,get
  • p admin,/api/base,post
  • p user,/api/base,get
  • g qimiao,admin
  • g qimiao,user
  • g qm admin
3.2、带域的
  • p admin,classOne,/api/base,get
  • g qimiao,admin,classOne
  • g qm,admin,classOne
  • g qimiao,admin,classTwo

十一、使用casbin

开始使用 | Casbin

1、本地文件模式初体验

  • 引入包 github.com/casbin/casbin/v2

  • 创建模型和policy 并且引入 e, err := casbin.NewEnforcer(“path/to/model.conf”, “path/to/policy.csv”)

  • 调用api并且使用

    sub := "alice" // the user that wants to access a resource.
    obj := "data1" // the resource that is going to be accessed.
    act := "read" // the operation that the user performs on the resource.ok, err := e.Enforce(sub, obj, act)if err != nil {
    // handle err
    }if ok == true {
    // permit alice to read data1
    } else {
    // deny the request, show an error
    }
    

案例

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

policy.csv

p,zhangsan,data1,read

mian.go

import ("fmt""github.com/casbin/casbin/v2"
)func main() {e, err := casbin.NewEnforcer("./model.conf", "./policy.csv")sub := "alice" // 想要访问资源的用户。obj := "data1" // 将被访问的资源。act := "read"  // 用户对资源执行的操作。added, err := e.AddPolicy("alice", "data1", "read")fmt.Println(added)fmt.Println(err)//进行检测ok, err := e.Enforce(sub, obj, act)if err != nil {// 处理errfmt.Printf("%s", err)}if ok == true {// 允许alice读取data1fmt.Println("通过")} else {// 拒绝请求,抛出异常fmt.Printf("未通过")}// 您可以使用BatchEnforce()来批量执行一些请求// 这个方法返回布尔切片,此切片的索引对应于二维数组的行索引。// 例如results[0] 是{"alice", "data1", "read"}的结果//results, err := e.BatchEnforce([[] []interface{}{"alice", "data1", "read"}, {"bob", datata2", "write"}, {"jack", "data3", "read"}})//e.BatchEnforce([][]interface{}{{"alice", "data1", "read"}, {"bob", "datata2", "write"}, {"jack", "data3", "read"}})
}

2、使用数据库存储policy

GitHub - casbin/gorm-adapter: Gorm adapter for Casbin

  • 使用gorm适配器进行存储

    • 引用包 github.com/casbin/gorm-adapter

    • a, _ := gormadapter.NewAdapter("mysql", "mysql_username:mysql_password@tcp(127.0.0.1:3306)/",true) // Your driver and data source.
      e, _ := casbin.NewEnforcer("examples/rbac_model.conf", a)
      连接数据库并自动建表
      
  • 初体验

    • // Load the policy from DB.e.LoadPolicy()
      
    • // Check the permission.
      e.Enforce("alice", "data1", "read")
      
    • // Modify the policy.
      // e.AddPolicy(...)// e.RemovePolicy(...)// Save the policy back to DB.
      e.SavePolicy()
      

案例

import ("fmt""github.com/casbin/casbin/v2"gormadapter "github.com/casbin/gorm-adapter/v3"_ "github.com/go-sql-driver/mysql"
)func main() {//e, err := casbin.NewEnforcer("./model.conf", "./policy.csv")a, _ := gormadapter.NewAdapter("mysql", "root:123456@tcp(127.0.0.1:3306)/casbin", true) // Your driver and data source.e, _ := casbin.NewEnforcer("./model.conf", a)sub := "alice" // 想要访问资源的用户。obj := "data1" // 将被访问的资源。act := "read"  // 用户对资源执行的操作。added, err := e.AddPolicy("alice", "data1", "read")fmt.Println(added)fmt.Println(err) //进行检测ok, err := e.Enforce(sub, obj, act)if err != nil {// 处理errfmt.Printf("%s", err)}if ok == true {// 允许alice读取data1fmt.Println("通过")} else {// 拒绝请求,抛出异常fmt.Printf("未通过")}}

3、对policy进行增删改查

  • filteredPolicy := e.GetFilteredPolicy(0, "alice")//第v0列是alice的数据
    
  • p AddPolicy()
    g e.AddGroupingPolicy("group1", "data2_admin")
    
  • removed := e.RemovePolicy("alice", "data1", "read")
    
  • updated, err := e.UpdatePolicy([]string{"eve", "data3", "read"}, []string{"eve", "data3", "write"})
    

4、自定义比较函数

函数 | Casbin

func KeyMatch(key1 string, key2 string) bool {return key1 == key2/*	i := strings.Index(key2, "*")if i == -1 {return key1 == key2}if len(key1) > i {return key1[:i] == key2[:i]}return key1 == key2[:i]*/
}func KeyMatchFunc(args ...interface{}) (interface{}, error) {name1 := args[0].(string)name2 := args[1].(string)return (bool)(KeyMatch(name1, name2)), nil
}

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) && my_func(r.obj, p.obj) && r.act == p.act
# m = g(r.sub, p.sub) && r.obj == p.obj && r.act == p.act
# m = r.sub == p.sub && r.obj == p.obj && r.act == p.act

相关内容

热门资讯

银河麒麟V10SP1高级服务器... 银河麒麟高级服务器操作系统简介: 银河麒麟高级服务器操作系统V10是针对企业级关键业务...
【NI Multisim 14...   目录 序言 一、工具栏 🍊1.“标准”工具栏 🍊 2.视图工具...
不能访问光猫的的管理页面 光猫是现代家庭宽带网络的重要组成部分,它可以提供高速稳定的网络连接。但是,有时候我们会遇到不能访问光...
AWSECS:访问外部网络时出... 如果您在AWS ECS中部署了应用程序,并且该应用程序需要访问外部网络,但是无法正常访问,可能是因为...
Android|无法访问或保存... 这个问题可能是由于权限设置不正确导致的。您需要在应用程序清单文件中添加以下代码来请求适当的权限:此外...
AWSElasticBeans... 在Dockerfile中手动配置nginx反向代理。例如,在Dockerfile中添加以下代码:FR...
北信源内网安全管理卸载 北信源内网安全管理是一款网络安全管理软件,主要用于保护内网安全。在日常使用过程中,卸载该软件是一种常...
月入8000+的steam搬砖... 大家好,我是阿阳 今天要给大家介绍的是 steam 游戏搬砖项目,目前...
​ToDesk 远程工具安装及... 目录 前言 ToDesk 优势 ToDesk 下载安装 ToDesk 功能展示 文件传输 设备链接 ...
ASM贪吃蛇游戏-解决错误的问... 要解决ASM贪吃蛇游戏中的错误问题,你可以按照以下步骤进行:首先,确定错误的具体表现和问题所在。在贪...