GORM钩子实现数据的加密存储

我正在参与掘金创作者训练营第4期,点击了解活动详情,一起学习吧!

需求的提出

大家是不是有数据库加密的需求,但是又不希望在业务代码中强编码实现数据的加密和解密?

使用java的同事可以使用mybatis的拦截器快速实现数据的加密和解密,那么golang有没有这么便捷的方法呢?

恭喜你,gorm的钩子也可以轻易实现数据的加密存储

letgo1.jpg

GORM的钩子

钩子是在创建、查询、更新、删除等操作之前、之后调用的函数。

如果您已经为模型定义了指定的方法,它会在创建、更新、查询、删除时自动被调用。如果任何回调返回错误,GORM 将停止后续的操作并回滚事务。

注意 在 GORM 中保存、删除操作会默认运行在事务上, 因此在事务完成之前该事务中所作的更改是不可见的,如果您的钩子返回了任何错误,则修改将被回滚。

1. 插入数据可以使用的勾子

```
// 开始事务
BeforeSave
BeforeCreate
// 关联前的 save
// 插入记录至 db
// 关联后的 save
AfterCreate
AfterSave
// 提交或回滚事务
```
复制代码

2. 更新数据可以使用的勾子

```
// 开始事务
BeforeSave
BeforeUpdate
// 关联前的 save
// 更新 db
// 关联后的 save
AfterUpdate
AfterSave
// 提交或回滚事务
```
复制代码

3. 删除数据可以使用的勾子

```
// 开始事务
BeforeDelete
// 删除 db 中的数据
AfterDelete
// 提交或回滚事务
```
复制代码

4. 查询数据时可以使用的勾子

```
// 从 db 中加载数据
// Preloading (eager loading)
AfterFind
```
复制代码

代码演示

下面演示的代码是我的一个日记应用,主要是加密了用户的日记信息

// 日志对象定义
import (
	"encoding/base64"
	"errors"
	"fmt"
	"sms/service/src/utils"
	"time"

	"github.com/jinzhu/gorm"
)

type UserDiary struct {
	Id         int64     `json:"id" `             //id serial primary key,
	Userid     int64     `json:"userid" `         //userid integer not null ,
	Year       int64     `json:"year" `           //year integer not null ,
	Month      int64     `json:"month" `          //month integer not null ,
	Day        int64     `json:"day" `            //day integer not null ,
	CreateTime time.Time `json:"create_time" `    //create_time timestamp default CURRENT_TIMESTAMP ,
	UpdateTime time.Time `json:"update_time" `    //update_time timestamp default CURRENT_TIMESTAMP,
	Status     int64     `json:"status" `         //status int not null default 0,
	Color      string    `json:"color" `          //color varchar(32) not null default 'orange',
	Diary      string    `json:"diary" enc:"aes"` //diary text
}

//保存前加密数据
func (diary *UserDiary) BeforeSave(db *gorm.DB) (err error) {
	utils.Log.Infof("Call UserDiary BeforeSave [%s]", diary.Diary)
	if diary.Diary != "" {
		keyname := fmt.Sprintf("user_code_%v", diary.Userid)
		key, ok := utils.GetCache(keyname) //每个用户都有自己独立的密钥,这里的缓存密钥来自用户登陆时
		if !ok {
			utils.Log.Errorf("GetCache(%s) failed!", keyname)
			return errors.New("获取用户密钥code失败!")
		}
		rs, err := utils.AesEcrypt([]byte(diary.Diary), []byte(utils.MD5(key.(string))))
		if err != nil {
			utils.Log.Errorf("AesEcrypt(%s) [%d] failed! %v", diary.Diary, diary.Userid, err)
			return err
		}
		diary.Diary = base64.StdEncoding.EncodeToString(rs)
	}
	return
}

//返回前解密数据
func (diary *UserDiary) AfterFind(tx *gorm.DB) (err error) {
	utils.Log.Info("Call UserDiary AfterFind")
	if diary.Diary != "" {
		b, err := base64.StdEncoding.DecodeString(diary.Diary)
		if err == nil {
			keyname := fmt.Sprintf("user_code_%v", diary.Userid)
			key, ok := utils.GetCache(keyname)
			if !ok {
				utils.Log.Errorf("GetCache(%s) failed!", keyname)
				return errors.New("获取用户密钥code失败!")
			}
			rs, err := utils.AesDeCrypt(b, []byte(utils.MD5(key.(string))))
			if err == nil {
				diary.Diary = string(rs)
			} else {
				utils.Log.Errorf("AesDeCrypt(%s) [%d] failed! %v", diary.Diary, diary.Userid, err)
			}
		} else {
			utils.Log.Errorf("DecodeStringB64(%s) failed! %v", diary.Diary, err)
		}
	}
	return
}
复制代码

代码就这么多,没有了!

真的,其他代码就和原来的一样不需要改变,是不是超棒!只要在你的数据模型类上加上加密和解密就可以了,是不超赞!快点使用使用失败吧~

这样就满足了么?

当然不!

这样只是初级的使用罢了,我这里还有更高级的方案哦!

1. 搭配golang的反射使用

注意我上面结构题定义的tag了么?

Diary      string    `json:"diary" enc:"aes"` //diary text
复制代码

我加了一个enc的tag,这个可以用来干啥呢? 通过反射读取tag,我们可以定义加密算法,或者在数据库外层调用解密,也就可以延迟解密,好处是啥?

你可以在你想要的地方解密,而不是在数据返回时就解密!

下面是反射的代码

func (diary *UserDiary) DoDec() {
	t := reflect.TypeOf(diary)
	for i := 0; i < t.NumField(); i++ {
		tag := t.Field(i).Tag.Get("enc")
		if tag != "" {
			b, err := base64.StdEncoding.DecodeString(diary.Diary)
			if err == nil {
				keyname := fmt.Sprintf("user_code_%v", diary.Userid)
				key, ok := utils.GetCache(keyname)
				if !ok {
					utils.Log.Errorf("GetCache(%s) failed!", keyname)
					return
				}
				rs, err := utils.AesDeCrypt(b, []byte(utils.MD5(key.(string))))
				if err == nil {
					diary.Diary = string(rs)
				} else {
					utils.Log.Errorf("AesDeCrypt(%s) [%d] failed! %v", diary.Diary, diary.Userid, err)
				}
			} else {
				utils.Log.Errorf("DecodeStringB64(%s) failed! %v", diary.Diary, err)
			}
		}
	}
}
复制代码

2. 有了tag还可以干啥?

  1. 自定义函数
  2. 数据鉴权
  3. 删除数据时自动备份
  4. 数据脱敏

更多的我就不在这里罗列了

想象空间

其实gorm的勾子和mybatis的拦截器没啥区别,struct的tag也可以和java的注解也没啥区别。借鉴gorm的勾子,我们是不是也可以在golang下搞一波面向切面(AOP)编程。

今天就到这里吧,have a nice weekend!

go1.jpg

猜你喜欢

转载自juejin.im/post/7066617839636971557