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

基于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操作。
这里推荐使用gorm的开源工具gormt github.com/xxjwxc/gormt, 可以将mysql数据库表结构自动生成golang sturct结构,带大驼峰命名规则json标签

根据git上文档的安装步骤安装即可,这里用的是window下的打包好可视化工具,直接下载使用,下载连接:https://github.com/xxjwxc/gormt/releases

gormt

下载解压之后可直接使用。如果是编译安装,则用编译后的(gormt.exe或者gormt)打开工具(看git文档)。

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

select_db

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

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

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

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

grpc_util中目录中创建rbac_handler目录,目录下创建rbac_handler.go脚本。
脚本内容,只贴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)
	}
}

执行脚本完成,可以用rbac.bat或者rbac.sh测试跑下脚本。
跟着把该脚本放入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.GetUserInfo") {
		http.Error(w, errors.New("not permission").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.GetUserInfo") {
		http.Error(w, errors.New("not permission").Error(), 500)
		return
	}
第六步:测试验证

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

gitee完整代码链接

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值