Gin RBAC 权限基础实现
RBAC (基于角色的访问控制) 是一种广泛应用的权限管理模型, 通过 角色
将 用户
和 权限
解耦, 简化权限分配管理.
- 用户 (User): 系统的使用者
- 权限 (Permission): 对资源 (页面, 按钮, API) 的增删改查许可
- 角色 (Role): 权限的集合
- 会话 (Session): 用户激活角色的临时上下文
它的层级按复杂度又分为 基础模型, 角色集成, 约束模型, 完整模型等, 在企业后台管理系统, 如 OA, ERP, SaaS 等应用广泛.
这里还是以上次的项目, 来进行一个类似商城后台相关的权限设计实现 (RBAC0).
用户表
drop table if exists `users`;
create table `manager` (
id int not null auto_increment comment '自增id',
user_id varchar(36) not null comment '对外暴露的uuid',
role_id int not null default 0 comment '角色',
is_super tinyint not null default 0 comment '是否为超级管理员',
status tinyint not null default 0 comment '0禁用, 1启用',
username varchar(50) unique not null comment '用户名',
password varchar(255) not null comment '用户哈希密码',
avatar varchar(255) default null comment '头像地址',
create_time datetime default current_timestamp comment '创建时间',
update_time datetime default current_timestamp on update current_timestamp comment '更新时间',
primary key (`id`)
);
-- 测试数据
insert into manager (`user_id`, `role_id`, `is_super`, `status`, `username`, `password`, `avatar`)
values ('abcde', 1, 0, 0, 'admin', 'admin123', null);
insert into manager (`user_id`, `role_id`, `is_super`, `status`, `username`, `password`, `avatar`)
values ('abcde', 1, 0, 0, 'admin2', 'admin123', null);
insert into manager (`user_id`, `role_id`, `is_super`, `status`, `username`, `password`, `avatar`)
values ('abcde', 1, 0, 0, 'admin3', 'admin123', null);
角色表
drop table if exists role;
create table role (
id int not null auto_increment comment '自增id'
, name varchar(50) unique not null comment '角色名称'
, descr varchar(255) default null comment '角色描述'
, create_time datetime default current_timestamp comment '创建时间'
, update_time datetime default current_timestamp on update current_timestamp comment '更新时间'
, primary key (`id`)
);
-- 测试数据
insert into role (`name`, `descr`) values ('超级管理员', '超级管理员');
insert into role (`name`, `descr`) values ('普通管理员', '普通管理员');
insert into role (`name`, `descr`) values ('普通用户', '普通用户');
权限表
drop table if exists rule;
create table rule(
id int not null auto_increment primary key comment '自增id'
, parent_id int default 0 comment '父级id'
, status tinyint default 1 comment '0关闭, 1启用'
, name varchar(50) default null comment '权限菜单名称'
, descr varchar(50) default null comment '前端路由名称'
, frontpath varchar(50) default null comment '前端路由注册路径'
, cond varchar(50) default null comment '菜单下的功能描述'
, menu tinyint default 1 comment '菜单或权限, 1菜单0权限'
, orders int default 50 comment '排序'
, icon varchar(100) default null comment '图标路径'
, method varchar(50) not null default 'GET' comment '请求方式'
, create_time datetime default current_timestamp
, update_time datetime default current_timestamp on update current_timestamp
);
-- 测试数据
-- 一级菜单, 后台面板, index
insert into `rule`(`parent_id`, `status`, `name`, `descr`, `frontpath`, `cond`, `menu`, `orders`, `icon`, `method`)
values (0, 1, '后台面板', 'index', null, null, 1, 1, 'help', 'GET');
-- 二级菜单, 后台面板/主控台, index, /
insert into `rule`(`parent_id`, `status`, `name`, `descr`, `frontpath`, `cond`, `menu`, `orders`, `icon`, `method`)
values (1, 1, '主控台', 'index', '/', null, 1, 20, 'home-filled', 'GET');
-- 二级菜单, 后台面板/bi看板, index, /
insert into `rule`(`parent_id`, `status`, `name`, `descr`, `frontpath`, `cond`, `menu`, `orders`, `icon`, `method`)
values (1, 1, 'bi看板', 'index', '/bi', null, 1, 20, 'home-filled', 'GET');
-- 一级菜单, 商品管理
insert into `rule`(`parent_id`, `status`, `name`, `descr`, `frontpath`, `cond`, `menu`, `orders`, `icon`, `method`)
values (0, 1, '商品管理', 'goods', null, null, 1, 2, 'shop-bag', 'GET');
-- 二级菜单, 商品模块/商品列表, /goods/list
insert into `rule`(`parent_id`, `status`, `name`, `descr`, `frontpath`, `cond`, `menu`, `orders`, `icon`, `method`)
values (4, 1, '商品列表', 'index', '/goods/list', null, 1, 20, 'home-filled', 'GET');
-- 二级菜单, 商品模块/分类管理, /category/list
insert into `rule`(`parent_id`, `status`, `name`, `descr`, `frontpath`, `cond`, `menu`, `orders`, `icon`, `method`)
values (4, 1, '分类管理', 'index', '/category/list', null, 1, 20, 'aim', 'GET');
-- 功能权限, 通过 menu = 0 区分
insert into `rule`(`parent_id`, `status`, `name`, `descr`, `frontpath`, `cond`, `menu`, `orders`, `icon`, `method`)
values (4, 1, '新增商品分类', 'goods', null, 'createCategory', 0, 20, null, 'GET');
insert into `rule`(`parent_id`, `status`, `name`, `descr`, `frontpath`, `cond`, `menu`, `orders`, `icon`, `method`)
values (4, 1, '获取商品分类', 'goods', null, 'getCategory', 0, 40, null, 'GET');
角色-权限表
drop table if exists `role_rule`;
create table `role_rule`(
`id` int not null auto_increment primary key comment '自增id'
, `role_id` int default null comment '角色id'
, `rule_id` int default null comment '权限id'
, create_time datetime default current_timestamp
, update_time datetime default current_timestamp on update current_timestamp
);
-- 测试数据
insert into `role_rule`(role_id, rule_id) values (2, 1), (3, 1);
insert into role_rule(role_id, rule_id) values (2, 4), (2, 1);
查询角色-菜单权限
-- 查询角色-菜单权限
select
parent.id as pid
, parent.name as p_name
, coalesce(parent.icon, '') as p_icon
, child.id
, coalesce(child.icon, '') as c_icon
, child.status
, child.name
, coalesce(child.descr, '') as descr
, child.frontpath
, coalesce(child.cond, '') as cond
, child.menu
, child.orders
, coalesce(child.method, '') as method
, child.create_time
from role_rule as rr
join rule as child on rr.rule_id = child.parent_id
join rule as parent on child.parent_id = parent.id
where rr.role_id = 2
and child.menu = 1
order by parent.parent_id
查询角色-功能操作权限
-- 操作权限
select
b.cond,
b.method
from role_rule a
inner join rule b on a.rule_id = b.parent_id
where a.role_id = 2
and b.parent_id is not null
and menu = 0
Gin 从JWT令牌解析出当前用户及角色ID存上下文
pkg/jwtt/jwt.go
type Claims struct {
UID int `json:"uid"`
RoleID int `json:"role_id"`
Username string `json:"username"`
Password string `json:"password"`
jwt.RegisteredClaims // 嵌入标准声明
}
Middleware/auth.go
// 从令牌中提取当前用户的 uid, roleId 作为上下文
c.Set("uid", claims.UID)
c.Set("roleId", claims.RoleID)
Gin 从中间件中获取当前用户及角色ID
utils/tools.go
package utils
import (
"errors"
"github.com/gin-gonic/gin"
)
// 从中间件上下文获取当前用户的 uid, roleId, 类型是 interface
func GetContextUser(c *gin.Context) (int, int, error) {
// 先获取接口数据, 再通过断言解析
uidInterface, ok := c.Get("uid")
if !ok {
return 0, 0, errors.New("用户认证失败")
}
uid, ok := uidInterface.(int)
if !ok {
return 0, 0, errors.New("用户认证失败")
}
// roleId
roleIdInterface, ok := c.Get("roleId")
if !ok {
return 0, 0, errors.New("用户权限不足")
}
roleId, ok := roleIdInterface.(int)
if !ok {
return 0, 0, errors.New("用户权限解析失败")
}
return uid, roleId, nil
}
Gin 获取用户及其权限接口实现
api/routers.go
package api
import (
"github.com/gin-gonic/gin"
"youge.com/api/handlers/auth"
"youge.com/api/handlers/user"
"youge.com/internal/middleware"
)
// 统一注册入口
func RegisterAllRouters(r *gin.Engine) {
// 登录认证模块
authGroup := r.Group("/api/auth")
{
// auth.POST("/register", Register)
authGroup.POST("/login", auth.Login)
}
// 用户管理模块
userGroup := r.Group("api/user")
// 需要 token 认证的哦
userGroup.Use(middleware.JWT())
{
userGroup.GET("/getinfo", user.GetAuthInfo)
}
}
api/handlers/user/menu.go
package user
import (
"time"
"github.com/gin-gonic/gin"
"youge.com/internal/db"
"youge.com/pkg/utils"
)
// 用户表
type User struct {
UID int `db:"id" json:"id"`
UserID string `db:"user_id" json:"user_id"`
RoleID int `db:"role_id" json:"role_id"`
IsSuper int `db:"is_super" json:"is_super"`
Status int `db:"status" json:"status"`
Username string `db:"username" json:"username"`
Password string `db:"password" json:"-"`
Avatar string `db:"avatar" json:"avatar"`
}
// 用户菜单权限, 数据集查询记录
type MenuItem struct {
PID int `db:"pid" json:"pid"`
PName string `db:"p_name" json:"p_name"`
PIcon string `db:"p_icon" json:"p_icon"`
ID int `db:"id" json:"id"`
Name string `db:"name" json:"name"`
Status int `db:"status" json:"status"`
Cicon string `db:"c_icon" json:"c_icon"`
FrontPath string `db:"frontpath" json:"frontpath"`
Descr string `db:"descr" json:"descr"`
Cond string `db:"cond" json:"cond"`
Menu int `db:"menu" json:"menu"`
Orders int `db:"orders" json:"orders"`
Method string `db:"method" json:"method"`
CreatedAt time.Time `db:"create_time" json:"create_time"`
}
// 子菜单
type Child struct {
ID int `db:"id" json:"id"`
Name string `db:"name" json:"name"`
Status int `db:"status" json:"status"`
Cicon string `db:"c_icon" json:"c_icon"`
FrontPath string `db:"frontpath" json:"frontpath"`
Descr string `db:"descr" json:"descr"`
Cond string `db:"cond" json:"cond"`
Menu int `db:"menu" json:"menu"`
Orders int `db:"orders" json:"orders"`
Method string `db:"method" json:"method"`
CreatedAt time.Time `db:"create_time" json:"create_time"`
}
// 返回给前端结构
type FrontendMenu struct {
PID int `json:"pid"`
PName string `json:"p_name"`
PIcon string `json:"p_icon"`
Children []Child `json:"children"` // 子菜单集合
}
// 菜单下的功能权限数据
type Condition struct {
Cond string `db:"cond" json:"cond"`
Method string `db:"method" json:"method"`
}
func buildMenuTree(menus []MenuItem) []FrontendMenu {
// 双索引结构:顺序保持 + 快速查找
orderedPIDs := make([]int, 0, 10) // 保持PID出现顺序
menuMap := make(map[int]*FrontendMenu) // 快速查找父节点
for _, item := range menus {
// 过滤无效数据, sql 层面其实已经处理了
if item.ID == 0 || item.PID <= 0 {
continue
}
// 初始化父容器(利用SQL的ORDER BY保证顺序)
if _, exist := menuMap[item.PID]; !exist {
menuMap[item.PID] = &FrontendMenu{
PID: item.PID,
PName: item.PName,
PIcon: item.PIcon,
Children: make([]Child, 0, 5), // 根据实际子项数调整
}
// 记录首次出现的PID顺序
orderedPIDs = append(orderedPIDs, item.PID)
}
// 添加子项
menuMap[item.PID].Children = append(
menuMap[item.PID].Children,
Child{
ID: item.ID,
Name: item.Name,
Status: item.Status,
Cicon: item.Cicon,
FrontPath: item.FrontPath,
Descr: item.Descr,
Cond: item.Cond,
Menu: item.Menu,
Orders: item.Orders,
Method: item.Method,
CreatedAt: item.CreatedAt,
},
)
}
// 按SQL原始顺序重组结果
tree := make([]FrontendMenu, 0, len(orderedPIDs))
for _, pid := range orderedPIDs {
if menu, exists := menuMap[pid]; exists {
tree = append(tree, *menu)
}
}
return tree
}
func getMenuData(roleId int) ([]MenuItem, error) {
// 将查询数据封装
var menus []MenuItem
mysql := `
select
parent.id as pid
, parent.name as p_name
, coalesce(parent.icon, '') as p_icon
, child.id
, coalesce(child.icon, '') as c_icon
, child.status
, child.name
, coalesce(child.descr, '') as descr
, child.frontpath
, coalesce(child.cond, '') as cond
, child.menu
, child.orders
, coalesce(child.method, '') as method
, child.create_time
from role_rule as rr
join rule as child on rr.rule_id = child.parent_id
join rule as parent on child.parent_id = parent.id
where rr.role_id = ?
and child.menu = 1
order by parent.parent_id
`
err := db.Query(&menus, mysql, roleId)
if err != nil {
return nil, err // 没数据, 返回空切片和错误
}
return menus, nil // 有数据, 返回数据和 nil 错误
}
func getRules(roleId int) ([]Condition, error) {
mysql := `
select
b.cond,
b.method
from role_rule a
inner join rule b on a.rule_id = b.parent_id
where a.role_id = ?
and b.parent_id is not null
and menu = 0
`
var conditions []Condition
err := db.Query(&conditions, mysql, roleId)
if err != nil {
return nil, err
}
return conditions, nil
}
func GetUser(uid int) (User, error) {
var user User
mysql := `
select
id
, user_id
, role_id
, is_super
, status
, username
, password
, coalesce(avatar, '') as avatar
from manager
where id= ?;
`
err := db.Get(&user, mysql, uid)
if err != nil {
return User{}, err // 失败时, 返回空用户和错误
}
return user, nil // 成功时, 返回用户信息和 nil
}
// 统一整合返回
func GetAuthInfo(c *gin.Context) {
// 01 获取当前用户的 uid, roleId
uid, roleId, err := utils.GetContextUser(c)
if err != nil {
utils.Error(c, 401, "", err)
return
}
// 02 获取用户信息
user, err := GetUser(uid)
if err != nil {
utils.BadRequest(c, "用户不存在")
return
}
// 03 获取菜单权限
menus, err := getMenuData(roleId)
if err != nil {
utils.Error(c, 400, "获取菜单数据失败")
return
}
// 将 flat menus 数据按 pid 转为 menusTree
menusTree := buildMenuTree(menus)
// 04 获取菜单下功能权限
conditions, err := getRules(roleId)
if err != nil {
utils.Error(c, 400, "获取菜单下的功能权限失败")
}
// 处理切片为 ["createUser,POST", "getUsers,GET", ....]
ruleList := make([]string, len(conditions))
for i, item := range conditions {
ruleList[i] = (item.Cond + "," + item.Method)
}
// 05 统一处理返回
utils.Success(c, gin.H{
"user": user,
"menus": menusTree,
"rules": ruleList,
})
}
还算是比较通用的, 里面很多都是用AI辅助编写的, 果然有了AI后, 真的是极大提升学习效率呀.
Gin RBAC 权限基础实现的更多相关文章
- ThinkPHP项目笔记之RBAC(权限)基础篇
今天,总结一下,RBAC(基于角色的访问控制),直白一点,就是权限管理.说到这,不得不“小叙”一下,我第一次 开发权限管理功能的“插曲”.第一次做这个,真的不会,我只知道“有点印象”,当时任务落到我的 ...
- yii2 rbac权限控制之菜单menu详细教程
作者:白狼 出处:http://www.manks.top/article/yii2_rbac_menu本文版权归作者,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则 ...
- ASP.NET 系列:RBAC权限设计
权限系统的组成通常包括RBAC模型.权限验证.权限管理以及界面访问控制.现有的一些权限系统分析通常存在以下问题: (1)没有权限的设计思路 认为所有系统都可以使用一套基于Table设计的权限系统.事实 ...
- RBAC权限模型——项目实战(转)
一.前言 权限一句话来理解就是对资源的控制,对web应用来说就是对url的控制,关于权限可以毫不客气的说几乎每个系统都会包含,只不过不同系统关于权限的应用复杂程序不一样而已,现在我们在用的权限模型基本 ...
- (转)RBAC权限模型——项目实战
一.前言 权限一句话来理解就是对资源的控制,对web应用来说就是对url的控制,关于权限可以毫不客气的说几乎每个系统都会包含,只不过不同系统关于权限的应用复杂程序不一样而已,现在我们在用的权限模型基本 ...
- Spring Security实现RBAC权限管理
Spring Security实现RBAC权限管理 一.简介 在企业应用中,认证和授权是非常重要的一部分内容,业界最出名的两个框架就是大名鼎鼎的 Shiro和Spring Security.由于Spr ...
- 基于RBAC权限验证, 中间价middleware实现, views 登录视图代码
废话不多说 上代码: 基础实现: rom django.shortcuts import HttpResponse, redirect, render from django.http import ...
- RBAC权限模型及数据权限扩展的实践
话说大家对RBAC权限模型应该是耳熟能详了.但真正用的好的并不多.并且原始的RBAC模型并不包括数据权限的管理,网上也差点儿没有相关的文章可以參考.本人经过几个项目的实战,在其基础上扩展出一套可行的. ...
- [七年技术总结系列][理论篇]-RBAC权限模型由浅入深
权限部分将分两章介绍,第一章由浅入深介绍权限理论知识及应用,第二章介绍具体实现.后期再讲述中间件的使用时,还会插入一些权限内容,本质上属于中间件的应用. 权限模块是业务系统最常见.最基本的子集.本章假 ...
- 两种RBAC权限控制模型详解
序言 由于最近一直卡在权限控制这个坎上,原来设计的比较简单的权限控制思路已经无法满足比较复杂一些的场景,因此一直在探索一种在大部分场景下比较通用的权限模型. 首先,这里说明一下两种RBAC权限模型分别 ...
随机推荐
- QT5笔记: 30. 二进制文件读写
Qt 预定义类型文件 *.stm 标准二进制文件 *.dat 例子: MainWindow.h #ifndef MAINWINDOW_H #define MAINWINDOW_H #include & ...
- Processing多窗口程序范例(二)
多窗口范例(二),做一个划线生成图像的应用,最后结果: 子窗口划线,主窗口复制多个画布叠加并添加了旋转动画. 范例程序 主程序: package syf.demo.multiwindow2; impo ...
- FastAPI路由与请求处理进阶指南:解锁企业级API开发黑科技 🔥
title: FastAPI路由与请求处理进阶指南:解锁企业级API开发黑科技 date: 2025/3/3 updated: 2025/3/3 author: cmdragon excerpt: 5 ...
- 震惊!AI 编程竟然让程序员 “失业” 了?真相让人意外
在科技飞速发展的当下,AI 编程的异军突起无疑成为了整个编程领域乃至社会各界热议的焦点. 去年,全球首个AI程序员Devin横空出世,不仅能独立完成代码开发.修复Bug,甚至能通过阅读技术文档自主学习 ...
- bp安装+匹配规则(防止抓火狐的多余包)
bp安装使用 BurpLoaderKeygen.jar: 2c8c7b95640f31985f83580402f26a06b78c55877fa33ef1f9d14d2ebb2d8ecd burpsu ...
- [I.1] 个人作业:阅读和提问
个人作业:阅读和提问 项目 内容 这个作业属于哪个课程 2025年春季软件工程(罗杰.任健) 这个作业的要求在哪里 [I.1] 个人作业:阅读和提问 我在这个课程的目标是 学习并掌握软件工程方法,与团 ...
- MySQL超大表删除数据过程
背景 笔者在公司负责公司的OpenAPI应用,估产生了调用审计的需求.对于存储这些AccessLog,虽然业界有很合适的架构和理论,奈何我司已成本优先,且作为toB的项目,调用量并不算特别大,每天也就 ...
- go cobra实例讲解
概述 cobra 库是 golang 的一个开源第三方库,能够快速便捷的建立命令行应用程序. 优势:cobra 可以快速建立CLI程序,使我们更专注于命令需要处理的具体的业务逻辑. 举两个例子: hu ...
- 【WPF开发】HandyControl Growl控件Error通知不自动消失的问题
需求 HandyControl Growl在Error类型的通知不自动消失,此时需要他跟其他的统一. 找寻原因 那么翻翻代码看看为啥不消失呗 1.这是决定关闭通知的计时器 2.这是通过_staysOp ...
- Win32控制台获取可执行程序的快捷方式的目标位置、起始位置、快捷键、备注等
Win32控制台获取可执行程序的快捷方式的目标位置.起始位置.快捷键.备注等,示例如下图: #include <iostream> #include <atlstr.h> #i ...