1 Star 2 Fork 2

toegg蛋/emicro_test

加入 Gitee
与超过 1200万 开发者一起发现、参与优秀开源项目,私有仓库也完全免费 :)
免费加入
文件
emicro_1/grpc_util
emicro_2
emicro_3
emicro_4
emicro_5
emicro_6
grpc_gateway
grpc_server
grpc_util
README.md
emicro_7
emicro_8
emicro_9
.gitignore
LICENSE
克隆/下载
贡献代码
同步代码
取消
提示: 由于 Git 不支持空文件夾,创建文件夹后会生成空的 .keep 文件
Loading...
README

基于go-micro微服务的实战-Gateway网关层的鉴权-rbac(六)


文章最后附带完整代码

上一节讲了身份认证。这节就接着讲鉴权授权,也就是访问权限,身份认证通过后对其授权是否有权限访问,不同用户具有的访问选项不同。A能访问a,b,c链接,B能访问b,c,d。 这里用的是常用的Rbac模型,主要三个主体,用户角色权限。三个主体的关系概括一句话:用户属于某个角色,某个角色具有某些权限

设计流程是这样

  1. 权限的变更频率少,所以把角色权限,也就是角色对应权限放到缓存Redis中。
  2. 采用定时任务cron,每分钟跑权限检测脚本
    2.1 有变更标识则强制刷新权限缓存(在后台设置);
    2.2 没变更则累计30分钟刷新一次权限缓存;
    2.3 上面两种情况都不符合,则判断角色权限是否为空,权限是否为空,空则加载对应缓存
  3. 用户登录的jwt自定义字段增加所属角色idRoleId
  4. 网关层Gateway身份认证猴,解析jwt中RoleId来获取Redis缓存中的角色权限,判断是否具有对应权限。
第一步:增加Rbac的数据表和结构体

创建Rbac的几张数据表,用户表之前已有,无需创建,直接用

CREATE TABLE `role` (
  `role_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '角色id',
  `name` varchar(50) NOT NULL COMMENT '角色名',
  PRIMARY KEY (`role_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='角色表';
INSERT INTO `role` VALUES ('1', '普通用户');
INSERT INTO `role` VALUES ('2', '管理员');

CREATE TABLE `method` (
  `id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '方法id',
  `name` varchar(50) NOT NULL COMMENT '方法名',
  `parent_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '所属微服务模块id',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='方法权限表';
INSERT INTO `method` VALUES ('1', 'user.TestUser', '1');
INSERT INTO `method` VALUES ('2', 'user.UserReg', '1');
INSERT INTO `method` VALUES ('3', 'user.UserLogin', '1');

CREATE TABLE `role_method` (
  `role_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '角色id',
  `method_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '方法id',
  PRIMARY KEY (`role_id`,`method_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='角色权限表';
INSERT INTO `role_method` VALUES ('1', '1');
INSERT INTO `role_method` VALUES ('1', '2');
INSERT INTO `role_method` VALUES ('1', '3');

CREATE TABLE `user_role` (
  `user_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '用户id',
  `role_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '角色id',
  PRIMARY KEY (`user_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='用户角色表';
INSERT INTO `user_role` VALUES ('43', '1');

//额外添加的,Rbac模型中只需上面4张
CREATE TABLE `service` (
  `id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '微服务模块id',
  `name` varchar(50) NOT NULL COMMENT '服务模块名',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='微服务模块表';
INSERT INTO `service` VALUES ('1', '用户服务模块');

创建完了表结构,需要把表结构也转化成Golang的结构体,方便gorm操作Db。这里推荐使用gorm的开源工具gormtgithub.com/xxjwxc/gormt, 可以将mysql数据库表结构自动生成golang sturct结构,带大驼峰命名规则。带json标签。 根据git上的安装步骤安装即可,我用的是window的可视化程序,直接下载使用。下载连接:https://github.com/xxjwxc/gormt/releases

安装完成之后,这里用的是window可视化工具,如果是编译安装,则用编译后的(gormt.exe或者gormt)打开工具(看git步骤)。

点击右上角的set配数据库等信息,点击refresh,就如下所示,可以直接复制struct,不符合预期则做修改即可,不用每个表都手动敲struct。把对应的Rbac结构体放到grpc_server/user/models/user_model.go模型中

第二步:增加定时刷新Redis权限缓存的脚本

grpc_util中目录中创建rbac_handler目录,目录下创建rbac_handler.go脚本

其中,Redis权限缓存的命名规则

	Redis的命名格式:
	--角色对应权限用set集合存储
	----格式key rbac_role_角色id
	----格式val []string{方法id}

	--服务方法用hash类型存储
	----格式 key:rbac_method
	----    field:rbac_method_方法名
	----    val:方法id

脚本内容,只贴main部分,逻辑业务则没贴出来,上gitee看完整代码

const(
	KEY_RBAC_REFRESH = "rbac_handler_refresh"		//强制刷新rbac的标识key
	KEY_RBAC_REFRESH_TIMES = "rbac_handler_times" 	//累计cron执行检测次数key

	KEY_RBAC_ROLE_PREFIX = "rbac_role_"				//角色对应权限的key前缀
	KEY_RBAC_METHOD = "rbac_method"					//服务方法hash结构的key值
	KEY_RBAC_METHOD_PREFIX = "rbac_method_"			//服务方法hash结构的field前缀
)

func main(){
    //创建Redis客户端
	addr := fmt.Sprintf("%v:%d", REDIS_ADDR, REDIS_PORT)
	redis := redis.NewClient(&redis.Options{Addr:addr})
	defer redis.Close()
	_, err := redis.Ping().Result()

	if err != nil {
		log.Println("err :", err)
		return
	}

	//是否有强制刷新标识(在后台设置)
	if redis.Get(KEY_RBAC_REFRESH).Val() == "1" {
		if refresh_rbac_handler(redis) == nil {
			redis.Del(KEY_RBAC_REFRESH)
		}
		return
	}

    //判断权限缓存中的方法缓存是否为空
	if redis.HLen(KEY_RBAC_METHOD).Val() <= 0 {
		refresh_rbac_methods(redis)
	}

	//判断权限缓存中的角色缓存是否为空,只判断角色1
	if redis.SCard(KEY_RBAC_ROLE_PREFIX + "1").Val() <= 0{
		refresh_rbac_role_methods(redis)
	}

	//30分钟则强制刷新一次权限缓存
	if redis.Get(KEY_RBAC_REFRESH_TIMES).Val() == "30"{
		if refresh_rbac_handler(redis)!= nil{
			return
		}
		redis.Set(KEY_RBAC_REFRESH_TIMES, 1, 0)
	}else{
		redis.Incr(KEY_RBAC_REFRESH_TIMES)
	}
}

执行脚本完成,把该脚本放入cron定时任务,1分钟执行一次监控检测

# vi /etc/crontab
*/1 * * * * root /home/tool/golearn/src/emicro_6/grpc_util/rbac_handler/rbac_handler
第三步:用户服务的注册接口和登录接口的调整

注册接口增加用户角色的记录,用gorm的事务操作Db

	//写进数据库
	user.Pwd = utils.Md5(user.Pwd)
	tx:= models.Db.Begin()
	result := tx.Create(&user)
	if result.Error != nil{
		tx.Rollback()
		return result.Error
	}

	userRole := models.UserRole{Id: user.Id, RoleId: common.DefaultRoleId}
	result = tx.Create(&userRole)
	if result.Error != nil{
		tx.Rollback()
		return result.Error
	}
	tx.Commit()

	resp.Status = common.RESP_SUCCESS
	resp.Msg = "success"

登录接口的调整,获取用户的角色,放到token中

    type result struct {
		UserId int32
		Pwd string
		RoleId int32
	}

	var res result
	models.Db.Table("user").Select("user.user_id, user.pwd, user_role.role_id").Joins("left join user_role on user.user_id = user_role.user_id").Where("user.phone = ?", req.Phone).Scan(&res)

	if utils.Md5(req.Pwd) != res.Pwd {
		resp.Status = common.RESP_ERROR
		resp.Msg = "auth error"
		return nil
	}

	if res.RoleId <= 0 {
		res.RoleId = common.DefaultRoleId
	}

	resp.Status = common.RESP_SUCCESS
	resp.Token, _ = utils.GetToken(res.UserId, res.RoleId, 600)

grpc_server/utils/jwt.go中jwt自定义字段新增角色idRoleId,以及jwt其它几个Api函数的适配。并同步修改到grpc_gateway/utils/jwt.go

type CustomClaims struct {
	UserId int32
	RoleId int32
	jwt.StandardClaims
}

func GetToken(user_id int32, role_id int32, expire int64) (string, error) {
	claims := CustomClaims{
		user_id,
		role_id,
		jwt.StandardClaims{
			ExpiresAt: time.Now().Unix()+expire,
			Issuer:    "admin",
		},
	}
	token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
	tokenStr, err := token.SignedString([]byte(secret))
	return tokenStr, err
}

...
第四步:网关层Gateway增加Redis连接池和Rbac权限机制

配置Redis连接配置 在grpc_gateway/conf/service.conf新增

redis_addr = "127.0.0.1"
redis_port = 6379

grpc_gateway目录下创建vendors目录,创建rbac目录和redis目录

redis连接池和操作,eredis.go

const(
	USING = 1
	FREE  = 2

	INITNUM = 20
	MAXNUM = 60

	PINGSTEP = 10  		//两次ping之间的间隔

	RETRY_TIMES = 3		//重试次数

	ALIVE_TIME = 7200	//连接存活时间上限,2个小时,看配置的timeout来设
)

var redisLockAddr = []string{}

type Conn struct{
	//Db *redis.ClusterClient		//集群对象
	Db *redis.Client 			//单机和主从哨兵对象
	status int
	pingTime int64				//最后一次ping的时间
	time int64					//初始化时间
}

type RedisPool struct{
	sync.RWMutex
	maxConnNum  int				//最大连接数
	initConnNum int				//初始连接数
	idleConns   chan *Conn		//未创建的连接(未初始化)
	cacheConns  chan *Conn		//已创建的空闲连接

	pushConnCount int64			//已放回的连接数量
	popConnCount  int64			//已取出的连接数量

	use_pool bool				//是否启用连接池
	close_status bool			//是否关闭状态
}

var ERedis *RedisPool

func init(){
	ERedis = OpenPool()
}

//开启连接池
func OpenPool() (*RedisPool){
	pool := newPool()
	for i:=0; i<pool.maxConnNum; i++{
		if i<pool.initConnNum {
			conn, err := pool.newConn()
			if err != nil{
				log.Println("OpenPool error:", err)
				pool.AddIdleConn()
				continue
			}
			pool.cacheConns <- conn
		}else{
			conn := new(Conn)
			pool.idleConns <- conn
		}
	}
	return pool
}

rbac权限机制, rbac.go,解析token后直接读取redis验证

//鉴权-权限验证
func RbacFilter(role_id int32, method_name string) bool{
	method_id, err := redis.ERedis.HGet("rbac_method", "rbac_method_" + method_name)
	if err != nil{
		return false
	}
	return redis.ERedis.SIsmember("rbac_role_" + utils.GetString(role_id), method_id)
}
第五步:网关层Gateway的handler处理鉴权

身份认证通过后,解析token进行权限验证,通过再继续往下走,转发rpc请求

func TestUserGet(w http.ResponseWriter, r *http.Request, params httprouter.Params) {
	//验证token
	token := r.Header.Get("Authorization")
	log.Println("token:",token)
	if err :=utils.VerityToken(token); err!=nil{
		http.Error(w, err.Error(), 500)
		return
	}

	//解析token参数
	user_id, role_id, err := utils.GetJwtData(token)
	if err !=nil{
		http.Error(w, err.Error(), 500)
		return
	}

	//权限验证
	if !rbac.RbacFilter(role_id, "user.TestUser") {
		http.Error(w, errors.New("not permission").Error(), 500)
		return
	}

授权只需添加解析和验证代码即可

	//解析token参数
	_, role_id, err := utils.GetJwtData(token)
	if err !=nil{
		http.Error(w, err.Error(), 500)
		return
	}

	//权限验证
	if !rbac.RbacFilter(role_id, "服务.方法名") {
		http.Error(w, errors.New("not permission").Error(), 500)
		return
	}
第六步:测试验证

测试验证这步跟随第5节的最后一步,同样测试方式即可。

马建仓 AI 助手
尝试更多
代码解读
代码找茬
代码优化
Go
1
https://gitee.com/toegg/emicro_test.git
git@gitee.com:toegg/emicro_test.git
toegg
emicro_test
emicro_test
master

搜索帮助

371d5123 14472233 46e8bd33 14472233