Go權限管理庫Casbin和身份驗證庫jwt-go初試

這篇文章是技術棧基於:gin + casbin + jwt-go 前期目標是實現簡單的基於角色的用戶權限管理系統,後期改形成提供權限判斷和身份驗證的微服務。html

對於gin框架能夠參照我寫的這篇文章: 基於Golang的開發框架Gin實戰前端

這篇文章構建的項目結構跟上一篇相似,惟一修改的地方是將 routes 路由文件夾下的web.go 文件拆分爲 web.goapi.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身份認證庫

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 裏測試結果以下:數據庫

經過JSON的數據格式交互,在body體裏經過json對象

{
	"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權限管理庫

官方文檔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

文章寫的比較粗糙,主要羅列了下實現重點,不懂的能夠交流下。

相關文章
相關標籤/搜索