Gin框架使用Casbin进行用户权限校验_gin 的权限校验-程序员宅基地

技术标签: golang  go  

以下是测试项目目录

在这里插入图片描述

一、配置model

conf/casbin_rbac_model.conf

# 请求
[request_definition]
r = sub,obj,act
# sub ——> 想要访问资源的用户角色(Subject)——请求实体
# obj ——> 访问的资源(Object)
# act ——> 访问的方法(Action: get、post...)

# 策略(.csv文件p的格式,定义的每一行为policy rule;p,p2为policy rule的名字。)
[policy_definition]
p = sub,obj,act
# p2 = sub,act 表示sub对所有资源都能执行act

# 组定义
[role_definition]
g = _, _
# _,_表示用户,角色/用户组
# g2 = _,_,_ 表示用户, 角色/用户组, 域(也就是租户)

# 策略效果
[policy_effect]
e = some(where (p.eft == allow))
# 上面表示有任意一条 policy rule 满足, 则最终结果为 allow;p.eft它可以是allow或deny,它是可选的,默认是allow

# 匹配器
[matchers]
m = r.sub == p.sub && keyMatch(r.obj,p.obj) && (r.act==p.act || p.act == "*") || r.sub=="superTest"
# r.sub="xxx"表示实体为superTest的直接通过


二、配置policy

官方文档的示例使用.csv文件来进行管理,这样不利于动态管理,这里我使用mysql数据库来进行存储

需要用到的包:

# 贴出的代码中保留了所有用到的import的包

controler/casbins.go

package controler

import (
	"casbin_test/logic"
	"casbin_test/models"
	"github.com/gin-gonic/gin"
	"github.com/gin-gonic/gin/binding"
	"net/http"
)

/**
 *@Method 添加规则入口函数
 *@Params
 *@Return
 *@Tips:
 */
func AddPHandler(c *gin.Context){
    
	var cb = new(models.AddPReq)
	err := c.ShouldBindWith(cb, binding.Form)
	if err != nil {
    
		c.JSON(http.StatusBadRequest,gin.H{
    
			"success":false,
			"msg":"求情参数异常",
		})
		return
	}
	_, err = logic.AddPLogic(cb)
	if err != nil {
    
		c.JSON(http.StatusOK,gin.H{
    
			"success":false,
			"msg":"添加失败",
		})
		return
	}
	c.JSON(http.StatusOK,gin.H{
    
		"success":true,
		"msg":"添加成功",
	})
}

logic/casbins.go

package logic

import (
	"casbin_test/dao"
	"casbin_test/models"
)

/**
 *@Method 添加规则逻辑函数
 *@Params
 *@Return
 *@Tips:
 */
func AddPLogic(cb *models.AddPReq) (bool, error) {
    
	e := dao.Casbin()
	//policy := e.GetPolicy()
	return e.AddPolicy(cb.RoleName, cb.Path, cb.Method) // 查看源码发现库中对已存在重复的规则进行了处理
}

dao/mysql.go

package dao

import (
	"fmt"
	"github.com/Blank-Xu/sqlx-adapter"
	"github.com/casbin/casbin/v2"
	_ "github.com/go-sql-driver/mysql"
	"github.com/jmoiron/sqlx"
	"log"
	"time"
)

var MDB *sqlx.DB

/**
 *@Method 数据库初始化
 *@Params
 *@Return
 *@Tips:
 */
func Init() (err error) {
    
	dsn := fmt.Sprintf("root:root@tcp(127.0.0.1:3306)/casbin_test?charset=utf8mb4&parseTime=true")
	MDB, err = sqlx.Connect("mysql", dsn)
	if err != nil {
    
		log.Fatal("connect MDB failed:",err.Error())
		return
	}
	// 设置最大连接数
	MDB.SetMaxOpenConns(20)
	MDB.SetMaxIdleConns(10)
	MDB.SetConnMaxLifetime(time.Minute*5)
	return
}

/**
 *@Method 关闭连接
 *@Params
 *@Return
 *@Tips:
 */
func CloseMysql(db *sqlx.DB) {
    
	err := db.Close()
	if err != nil {
    
		panic(err)
	}
}

/**
 *@Method 实例化
 *@Params
 *@Return
 *@Tips:
 */
func Casbin() *casbin.Enforcer{
    
	var err error
	a, err := sqlxadapter.NewAdapter(MDB, "casbin_rule")
	if err != nil {
    
		log.Fatal(err.Error())
	}
	e, err := casbin.NewEnforcer("./conf/casbin_rbac_model.conf", a)
	if err != nil {
    
		log.Fatal(err.Error())
	}
	// 加载规则
	err = e.LoadPolicy()
	if err != nil {
    
		fmt.Printf("加载失败,error:%s",err.Error())
	}
	return e
}

models/casbin_model.go

package models

type AddPReq struct {
    
	RoleName string `form:"rolename" binding:"required"`
	Path     string `form:"path" binding:"required"`
	Method   string `form:"method" binding:"required"`
}

三、配置jwt和权限校验中间件,模拟用户登录

pkg/jwt/jwt.go

package jwt

import (
	"errors"
	"github.com/dgrijalva/jwt-go"
	"time"
)

const (
	AcceptTokenKey             = "ACToken"
	RefreshTokenKey            = "RFToken"
	TokenIssuer                = "testIssuer"
	mySecret                   = "666test"
	TokenExpireDuration        = time.Hour * 24 *7
	RefreshTokenExpireDuration = time.Hour * 24 * 30
)

var (
	// 预设错误信息
	TokenExpired     = errors.New("Token is expired")
	TokenNotValidYet = errors.New("Token not active yet")
	TokenMalformed   = errors.New("That's not even a token")
	TokenInvalid     = errors.New("Couldn't handle this token:")
)

type MyClaims struct {
    
	UserName string `json:"username"`
	Role string `json:"role"`
	jwt.StandardClaims
}

func GenAccessToken(userName,role string) (aToken string, err error) {
    
	// 创建一个我们自己的声明的数据
	cl := MyClaims{
    
		UserName: userName,
		Role: role,
		StandardClaims: jwt.StandardClaims{
    
			ExpiresAt: time.Now().Add(TokenExpireDuration).Unix(), // 过期时间
			Issuer:    TokenIssuer,
		},
	}
	// 使用指定的签名方法创建签名对象
	aToken, err = jwt.NewWithClaims(jwt.SigningMethodHS256, cl).SignedString([]byte(mySecret))
	return
}

func GenRefreshToken() (rToken string, err error) {
    
	// refresh token
	rToken, err = jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.StandardClaims{
    
		ExpiresAt: time.Now().Add(RefreshTokenExpireDuration).Unix(),
		Issuer:    TokenIssuer,
	}).SignedString([]byte(mySecret))
	return
}

func ParseToken(tokenString string) (*MyClaims, error) {
    
	// 解析token
	var mc = new(MyClaims)
	token, err := jwt.ParseWithClaims(tokenString, mc, func(token *jwt.Token) (interface{
    }, error) {
    
		return []byte(mySecret), nil
	})
	if err != nil {
    
		// 如果强转*jwt.ValidationError成功,对错误进行判断
		if validationError, ok := err.(*jwt.ValidationError); ok {
    
			/*
				当validationError中的错误信息由错误的token结构引起时,
				**************************************************
				源码vErr.Errors |= ValidationErrorExpired,
				或运算,只有都为0才为0,0000 0000|0000 0101 = 0000 0101
				由于vErr.Errors的初始值为0,所以等价于将ValidationErrorMalformed赋值给validationError的Errors,
				*****************************************************
				如果没有赋值,Errors的初始值为0,那么validationError.Errors&jwt.ValidationErrorMalformed = 0,
				赋值后造成validationError.Errors不为0,那么validationError.Errors&jwt.ValidationErrorMalformed != 0
			*/
			if validationError.Errors&jwt.ValidationErrorMalformed != 0 {
    
				return nil, TokenMalformed
				// 以下与上方原理相同
			} else if validationError.Errors&jwt.ValidationErrorExpired != 0 {
    
				return nil, TokenExpired
			} else if validationError.Errors&jwt.ValidationErrorNotValidYet != 0 {
    
				return nil, TokenNotValidYet
			} else {
    
				return nil, TokenInvalid
			}
		}
	}
	if token != nil {
    
		// 强转成jwtClaims
		if claims, ok := token.Claims.(*MyClaims); ok && token.Valid {
    
			// 如果合法返回claims
			return claims, nil
		}
		return nil, TokenInvalid
	} else {
    
		return nil, TokenInvalid
	}
}

func RefreshToken(aToken, rToken string) (newAToken string, err error) {
    
	// refresh token 无效直接返回
	if _, err = jwt.Parse(rToken, func(token *jwt.Token) (interface{
    }, error) {
    
		return []byte(mySecret), nil
	}); err != nil {
    
		return
	}

	// 从旧的token解析出claims数据
	var claims = new(MyClaims)
	_, err = jwt.ParseWithClaims(aToken, claims, func(token *jwt.Token) (interface{
    }, error) {
    
		return []byte(mySecret), nil
	})
	v, _ := err.(*jwt.ValidationError)
	if v.Errors == jwt.ValidationErrorExpired {
    
		return GenAccessToken(claims.UserName,claims.Role)
	}
	return
}

middleware/auth.go

package middleware

import (
	"github.com/gin-gonic/gin"
	myJwt "casbin_test/pkg/jwt"
	"net/http"
)

func JWTAuthMiddleWare() gin.HandlerFunc {
    
	return func(ctx *gin.Context) {
    
		// 获取前端传回的token(传递方式不同,获取的位置也不同,根据实际情况选择)
		authHeader := ctx.Request.Header.Get(myJwt.AcceptTokenKey)
		// 无token直接返回错误
		if authHeader == "" {
    
			ctx.JSON(http.StatusOK, gin.H{
    
				"success": false,
				"msg":     "未登录或非法访问",
			})
			// 校验失败终止后续操作
			ctx.Abort()
			return
		}
		// 解析token
		claims, err := myJwt.ParseToken(authHeader)
		// 错误处理
		if err != nil {
    
			ctx.JSON(http.StatusOK, gin.H{
    
				"success": false,
				"msg":     err.Error(),
			})
			ctx.Abort()
			return
		}
		// 将claim加入上下文,便于后续使用
		ctx.Set("userClaim", claims)
		ctx.Next()
	}
}

middleware/permission.go

package middleware

import (
	"casbin_test/dao"
	myJwt "casbin_test/pkg/jwt"
	"github.com/gin-gonic/gin"
	"net/http"
)

func PermissionMiddleWare() gin.HandlerFunc {
    
	return func(c *gin.Context) {
    
		// 获取claim
		claim := c.MustGet("userClaim").(*myJwt.MyClaims)
		e := dao.Casbin()
		// 检查用户权限
		isPass, err := e.Enforce(claim.Role, c.Request.URL.Path, c.Request.Method)
		if err != nil {
    
			c.JSON(http.StatusOK, gin.H{
    
				"success": false,
				"msg":     err.Error(),
			})
			c.Abort()
			return
		}
		if isPass {
    
			c.Next()
		} else {
    
			c.JSON(http.StatusOK, gin.H{
    
				"success": false,
				"msg":     "无此权限",
			})
			c.Abort()
			return
		}
	}
}

models/users.go

package models

type LoginReq struct {
    
	UserName string `form:"username" binding:"required"`
	Password string `form:"password" binding:"required"`
}

controler/user.go

package controler

import (
	"casbin_test/models"
	myJwt "casbin_test/pkg/jwt"
	"github.com/gin-gonic/gin"
	"github.com/gin-gonic/gin/binding"
	"net/http"
)

var (
	name1       = "熊大"
	name2       = "熊二"
  name3       = "光头强"
	nameRoleMap = map[string]string{
    
		"熊大": "superTest",
		"熊二": "admin",
		"光头强": "staff",
	}
)

// 模拟登陆
func Login(c *gin.Context) {
    
	var u = new(models.LoginReq)
	err := c.ShouldBindWith(u, binding.Form)
	if err != nil {
    
		c.JSON(http.StatusOK, gin.H{
    
			"success": false,
			"msg":     "参数错误",
		})
		return
	}
	if u.UserName == name1 || u.UserName == name2 || u.UserName == name3 && u.Password == "123456" {
    
		// 获取token
		token, err := myJwt.GenAccessToken(u.UserName, nameRoleMap[u.UserName])
		if err != nil {
    
			c.JSON(http.StatusOK, gin.H{
    
				"success": false,
				"msg":     "对不起我崩了!",
			})
		}
		c.JSON(http.StatusOK, gin.H{
    
			"success": true,
			"msg":     "登陆成功",
			"data":    token,
		})
	} else {
    
		c.JSON(http.StatusOK, gin.H{
    
			"success": false,
			"msg":     "用户名或密码错误!",
		})
	}

}

main.go

package main

import (
	"casbin_test/controler"
	"casbin_test/dao"
	"casbin_test/middleware"
	"github.com/gin-gonic/gin"
	"log"
	"runtime"
)

func main() {
    
	err := dao.Init()
	if err != nil {
    
		log.Fatal(err.Error())
	}
	runtime.SetFinalizer(dao.MDB, dao.CloseMysql)
	router := gin.Default()
	r := router.Group("/api")
	r.POST("/login", controler.Login)
	r.Use(middleware.JWTAuthMiddleWare()).Use(middleware.PermissionMiddleWare()) // 测试时可先注释,先添加规则
	r.POST("/addcasbin",controler.AddPHandler)

	router.Run(":8081")

}

四、使用postman测试效果

此时我预先只配置了一个规则:

在这里插入图片描述

模拟登录中预设的user和role对应关系为:

在这里插入图片描述

按照匹配的规则,应该只有熊二可以执行/api/addcasbin,但是由于在匹配其中增加了r.sub==“superTest”,所以熊大不需要经过校验就能获取该资源

看看postman测试结果是否和我们配置的一致:

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/weixin_45566022/article/details/108880608

智能推荐

Qt知识点总结_qt知识分享-程序员宅基地

文章浏览阅读1.9k次,点赞5次,收藏37次。QT提供了一些机制来保证线程安全,如互斥量(QMutex)、信号量(QSemaphore)和读写锁(QReadWriteLock)。这些机制可以在多线程环境下实现数据的同步访问和线程间的协调。_qt知识分享

spark原理简介_spark技术原理-程序员宅基地

文章浏览阅读773次。spark简介以及原理spark简介 spark是基于内存的分布式处理框架,它把要执行的作业拆分成多个任务,然后将任务分发到多个CPU进行处理,处理结果的中间数据存储在内存中,减少了数据处理过程中对硬盘的I/O操作,大大提升了处理效率。spark和MapReduce对比 spark相对于mr,性能上提高了100倍。 &_spark技术原理

大数据Hadoop入门_hadoop只用一个账户能启动-程序员宅基地

文章浏览阅读4.6k次,点赞4次,收藏34次。先安装好vmware并且创建一台虚拟机,IP和主机名配置1.点击vmware的“编辑” =>虚拟网络编辑器(N)...2.点击“VMnet8"后点击”更改设置“3.而后再次点击VMnet8,修改子网IP地址为:192.168.10.0(IP可以任意取值只要不为192.168.1.0即可)4.修改完成后,点击NAT设置,将网关的地址修改与子网IP在同一网段。这里网关IP设置为192.168.10.2 ;随后点击确定=>确定5.配置主机的IP,网..._hadoop只用一个账户能启动

LM series vertical grinding mill complete system of professional choice-程序员宅基地

文章浏览阅读82次。LM series verticalroller mill machine is widely absorb the advanced technology in thefoundation of internat..._lm coal mill

概念理解:面向对象编程(OOP)-程序员宅基地

文章浏览阅读49次。一、对象的综述面向对象编程(OOP)具有多方面的吸引力。对管理人员,它实现了更快和更廉价的开发与维护过程。对分析与设计人员,建模处理变得更加简单,能生成清晰、易于维护的设计方案。对程序员,对象模型显得如此高雅和浅显。此外,面向对象工具以及库的巨大威力使编程成为一项更使人愉悦的任务。每个人都可从中获益,至少表面如此。所有编程语言的最终目的都是解决企业又或者人在现实生活中所遇到的问题,最初我们..._咋样理解面向对象编程 -baijiahao

C语言之makefile简介及简单应用_c makefile-程序员宅基地

文章浏览阅读963次。其实makefile最大的优点就是提供了”自动编译”,只要内容编写好,一个make命令,整个工程就会自动编译程序,大大提高了软件开发的效率。 所以 会使用makfile还是一个很重要的技能哦!!!make工具最主要也是最基本的功能就是通过makefile文件来描述源程序之间的相互关系并自动维护编译工作。而makefile 文件需要按照某种语法进行编写,文件中需要说明如何编译各个源文件并连接生..._c makefile

随便推点

ls路由算法_计算机网络之链路状态路由选择算法(LS)-程序员宅基地

文章浏览阅读2.8k次。一、准备知识链路状态路路由选择算法是一种全局式路由选择算法。在此算法中,我们是假设所有网络拓扑和链路费用都是已知的(实践中通常是通过让每个结点向网络中所有其他节点广播链路状态分组来完成的)【OSPF协议】,通过节点广播使所有结点具备了该网络等同的完整视图。获得视图之后,通过LS算法可以计算出从源节点到网络任意结点的最低费用路径。.我们下面给出的链路状态路由选择算法叫做Dijkstra算法,在了解此..._ls路由

个人永久性免费-Excel催化剂功能第59波-快速调用Windows内部常用工具命令-程序员宅基地

文章浏览阅读60次。Windows里一些常用的工具、命令,许多存放得很深的位置,不容易找到,每次还要百度半天才能调用成功,Excel催化剂现将常用的操作,提取至插件中完成,一键即可调出相应功能,无需苦苦找寻。使用场景每每需要对Windows系统作一些设置时,不同的Windows版本设置菜单位置不一样,特别是现在的Win10坑爹地很,更难找到,用熟一个系统,跳到其他系统又找一大轮,微软这德性还..._excel 调用windows命令

leetcode 509.斐波那契数 C语言 动态规划_动态规划求解斐波那契数列c语言-程序员宅基地

文章浏览阅读1.2k次。题目读题从第三项开始,每一项是前两项的和,给定数n,求F(n)。列出前五项 F(0) F(1) F(2) F(3) F(4) F(n) 0 1 1 2 3 F(n-1)+F(n-2) 1+0 1+1 2+1 运用动态规划设计状态:从第三项开始每一次求第n项都转移到求n-1项和n-2项写出状态转移方程:F(n)=F(n-1)+F(n-2_动态规划求解斐波那契数列c语言

Windows下安装Hive(包安装成功)_windows安装hive-程序员宅基地

文章浏览阅读5.1k次,点赞12次,收藏28次。Hive 的Hive_x.x.x_bin.tar.gz 高版本在windows 环境中缺少 Hive的执行文件和运行程序。配置文件目录(%HIVE_HOME%\conf)有4个默认的配置文件模板拷贝成新的文件名。可以发现,自动连接MySQL去创建schema hive,并执行脚本。可以通过访问namenode和HDFS的Web UI界面(以及resourcemanager的页面(先在Hive安装目录下建立。根据自己的Hive安装路径(根据自己的Hive安装路径(请严格按照版本来安装。在Hadoop管理台(_windows安装hive

无偏估计+求样本的方差为何除n-1_设总体x~n(θ,1),gθ=θ2,试求gθ的无偏估计的方差下界-程序员宅基地

文章浏览阅读464次。$$f(x)={xˉ=∑1nxin均值S2=∑1n(x−xi)2n−1方差f(x)=\begin{cases} \bar{x}=\frac{ \sum_{1}^{n} x_{i} }{n}& \text{均值}\\S^2= \frac{ \sum_{1}^{n} (x- x_{i} )^2 }{n-1} & \text{方差}\end{cases}f(x)={xˉ=n∑1n​xi​​S2=n−1∑1n​(x−xi​)2​​均值方差​求样本方差为什么除n−1?求样本方差_设总体x~n(θ,1),gθ=θ2,试求gθ的无偏估计的方差下界

Linux学习教程,Linux入门教程(超详细)| 网址推荐_linux中大量使用脚本语言,而不是c语言!-程序员宅基地

文章浏览阅读1.3k次,点赞2次,收藏51次。1Linux简介2Linux安装3Linux文件和目录管理4Linux打包(归档)和压缩5Vim文本编辑器6Linux文本处理(Linux三剑客)7Linux软件安装8Linux用户和用户组管理9Linux权限管理10Linux文件系统管理11Linux高级文件系统管理12Linux系统管理13Linux备份与恢复14Linux系统服务管理15Linux系统日志管理16Linux启动管理17LAMP环境搭建和LNMP环境搭建18SEL..._linux中大量使用脚本语言,而不是c语言!