這篇文章是技術棧基於: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裏演示效果以下:
在 header頭裏填入/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
文章寫的比較粗糙,主要羅列了下實現重點,不懂的能夠交流下。