文章目录
- 零、知识补充
- 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()
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,
})
})
4、DELETE 删
一般情况为uri 同样也可以用body
r.DELETE("/path/:id", func(c *gin.Context) {
id := c.Param("id")
c.JSON(200, gin.H{
"id": id,
})
})
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,
})
})
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/你好/true
err := c.ShouldBindQuery(&p) //restBind?name=qimiao&sex=true
if 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、ShouldBindQuery
Name 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 PostParams
err := c.ShouldBindJSON(&p)
//err := c.ShouldBindUri(&p) //restBind/你好/true
//err := c.ShouldBindQuery(&p) //restBind?name=qimiao&sex=true
fmt.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,走完走1
fmt.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 Userinfo db.First(&user, "name=?", "辉**") fmt.Println(user)
-
查: Find (跟结构体切片指针地址,查符合条件的所有)
var user []Userinfo db.Find(&user) //不跟条件,查所有 fmt.Println(user)
-
条件:Where Or 填写简单的sql查询语句执行得到model
-
模型:Model
import (
"gorm.io/driver/mysql"
"gorm.io/gorm"
)
type Userinfo struct {
Id uint
Name string
Gender string
Hobby string
gorm.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 []Userinfo
db.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.Model
Name 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.Model ClassName string Students []Student //班级里有多个学生 } type Student struct { gorm.Model StudentName string // 学生属于classId班级 ClassID uint //一个学生一个学生卡 IdCard IdCard //多对多 学生有多个老师,并且知道老师的id Teachers []Teacher `gorm:"many2many:Student_Teachers"` //TeacherID uint } type IdCard struct { gorm.Model StudentID uint Num int } type Teacher struct { gorm.Model TeacherName string //老师有多个学生,老师知道学生的id //StudentID uint Students []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.Model
ClassName string
Students []Student //班级里有多个学生
}
type Student struct {
gorm.Model
StudentName string
// 学生属于classId班级
ClassID uint
//一个学生一个学生卡
IdCard IdCard
//多对多 学生有多个老师,并且知道老师的id
Teachers []Teacher `gorm:"many2many:Student_Teachers"`
//TeacherID uint
}
type IdCard struct {
gorm.Model
StudentID uint
Num int
}
type Teacher struct {
gorm.Model
TeacherName string
//老师有多个学生,老师知道学生的id
//StudentID uint
Students []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 Student
db.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 Class
db.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解成秒 60s
ExpiresAt: 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解成秒 60s
ExpiresAt: 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校验是否能校验通过
1.2、role_definition 角色域
- g = _ , _ 表示以角色为基础(用户是哪个角色)
- g=_, _, _ 表示以域为基础(多商户模式)(用户是哪个角色 属于哪个商户)
加完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
先匹配,然后根据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 {
// 处理err
fmt.Printf("%s", err)
}
if ok == true {
// 允许alice读取data1
fmt.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 {
// 处理err
fmt.Printf("%s", err)
}
if ok == true {
// 允许alice读取data1
fmt.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