这篇文章是技术栈基于:gin + casbin + jwt-go 前期目标是实现简单的基于角色的用户权限管理系统,后期改形成提供权限判断和身份验证的微服务。html
对于gin框架能够参照我写的这篇文章: 基于Golang的开发框架Gin实战前端
这篇文章构建的项目结构跟上一篇相似,惟一修改的地方是将 routes 路由文件夹下的web.go 文件拆分为 web.go
和 api.go
, 也就是将web路由和api接口路由分开了。若是熟悉Laravel框架的人确定知道这就是模仿Laravel项目结构建立的。mysql
web.go具体代码以下:git
package routes
import (
"github.com/gin-gonic/gin"
"accbase/app/Controllers"
)
func InitRouter() *gin.Engine{
r := gin.Default()
r.Static("/public", "./public") // 静态文件服务
r.LoadHTMLGlob("views/**/*") // 载入html模板目录
// web路由
r.GET("/", Controllers.Home)
r.GET("/about", Controllers.About)
r.GET("/post", Controllers.Post)
r.GET("/user", Controllers.User)
// api 路由
InitApi(r) // 这里是关键点,会初始化api路由
return r
}
复制代码
api.go文件内容以下:github
主要是 InitApi 函数的封装web
package routes
import (
"github.com/gin-gonic/gin"
"accbase/app/Controllers"
)
func InitApi(r *gin.Engine){
// 简单的api路由组: v1
v1 := r.Group("/api")
{
v1.GET("/ping", Controllers.Ping)
v1.POST("/token", Controllers.GetToken)
v1.POST("/user/info", Controllers.UserInfo)
v1.POST("/user/create", Controllers.UserCreate)
v1.POST("/user/delete", Controllers.UserDestroy)
v1.POST("/user/update", Controllers.UserUpdate)
v1.GET("/users", Controllers.UserFindAll)
}
}
复制代码
jwt-go仓库地址算法
先实现token令牌办法函数:sql
type Params struct {
Username string `json:"username"`
Password string `json:"password"`
}
func GetToken(c *gin.Context) {
var json Params
err := c.BindJSON(&json)
if err != nil {
fmt.Printf("mysql connect error %v", err)
return
}
hmacSampleSecret := []byte("my_secret_key")
token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{
"username": json.Username,
"password": json.Password, // 21219256@qq.com
"nbf": time.Date(2015, 10, 10, 12, 0, 0, 0, time.UTC).Unix(),
})
tokenString, err := token.SignedString(hmacSampleSecret)
fmt.Println(tokenString, err)
c.JSON(200, gin.H{
"token": tokenString,
})
}
复制代码
路由/api/token
指向的控制器是 Controllers包下的GetToken 函数,也就是上面代码片断。在postman 里测试结果以下:数据库
{
"username":"winyh",
"password":"2712191010@qq.com"
}
复制代码
通过后台jwt-go库对这两个字段和my_secret_key
作算法加密,颁发token令牌以下:json
{
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYmYiOjE0NDQ0Nzg0MDAsInBhc3N3b3JkIjoiMjcxMjE5MTAxMEBxcS5jb20iLCJ1c2VybmFtZSI6IndpbnloIn0.rXKxC1P9wMUlUvK2clf45oEban--7D4nZ37Ehx-CGmU"
}
复制代码
平常开发过程当中,前端工程师经过用户名,密码/邮箱等信息经过登陆接口获取token,而后存储到本地。每次请求的时候,放在 header头里,后端获取 header 头里的 token和先后端约定的加密串my_secret_key
.来识别(逆向解析)当前请求对象的信息。接下来演示下,经过token值获取当前用户信息。
路由/api/user/info
指向的控制器是 Controllers包下的UserInfo 函数,也就是下面的函数代码片断
func UserInfo(c *gin.Context) {
hmacSampleSecret := []byte("my_secret_key")
tokenString := c.Request.Header.Get("token")
// tokenString := "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJmb28iOiJiYXIiLCJuYmYiOjE0NDQ0Nzg0MDB9.-BRTwjN-sAlUjO-82qDrNHdMtGAwgWH05PrN49Ep_sU"
token, err := jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) {
// Don't forget to validate the alg is what you expect: if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok { return nil, fmt.Errorf("Unexpected signing method: %v", token.Header["alg"]) } // hmacSampleSecret is a []byte containing your secret, e.g. []byte("my_secret_key") return hmacSampleSecret, nil }) if claims, ok := token.Claims.(jwt.MapClaims); ok && token.Valid { fmt.Println(claims["username"], claims["nbf"]) c.JSON(200, gin.H{ "username": claims["username"], "password": claims["password"], }) } else { fmt.Println(err) c.JSON(200, gin.H{ "message": "获取失败!", }) } } 复制代码
在Postman里演示效果以下:
/api/token
接口里返回的token值,成功返回了当前用户信息。
官方文档Casbin
刚开始看这个库的时候是一脸懵逼,感受好高大上的样子。后来按照文档,一点点理解,发现实现起来仍是很方方便的。这个库实现的是权限管理,并不实现身份认证,因此上文将身份认证的事情交给了 jwt-go 库来实现。
两个核心概念:
访问控制模型model和策略policy
须要配置两个文件
控制模型:model.conf
[policy_definition]
p = sub, obj, act
[role_definition]
g = _, _
[policy_effect]
e = some(where (p.eft == allow))
[matchers]
m = g(r.sub, p.sub) && r.obj == p.obj && r.act == p.act
复制代码
策略:policy.csv
p, alice, data1, read
p, alice, data1, write
p, bob, data2, write
p, data2_admin, data2, read
p, data2_admin, data2, write
g, alice, data2_admin
复制代码
我测试的时候这两个文件方在本地casbin文件夹,用接口 /api/ping
来作访问测试,这个接口对应函数以下:
func Ping(c *gin.Context) {
e := casbin.NewEnforcer("./casbin/model.conf", "./casbin/policy.csv")
sub := "alice" // the user that wants to access a resource.
obj := "data1" // the resource that is going to be accessed.
act := "write" // the operation that the user performs on the resource.
// 注意点:这里的数据一把都是从数据库拿到的 ,数据库里记录了模型文件对应的策略信息。Casbin 根据策略字段判断是否有权限
var msg string
if e.Enforce(sub, obj, act) == true {
fmt.Println("进入了")
msg = "进入了"
} else {
fmt.Println("拒绝了")
msg = "无权访问"
}
c.JSON(200, gin.H{
"message": msg,
})
}
复制代码
Postman 测试截图以下:
若是把策略文件里把 alice 对 data1 的操做权限 write 改成 read ,再次访问就会被拒绝了。
p, alice, data1, read
最后放一下源码仓库:项目地址:https://github.com/YHWL/accbase
文章写的比较粗糙,主要罗列了下实现重点,不懂的能够交流下。