Dynamic RSA System design and implementation

1. Background

After implementing the static encryption and decryption tool, I felt that it was not flexible enough. I wanted to design an intermediate system that dynamically generates RSA KeyPair, temporarily called Dynamic RSA System, to achieve the effect of automatically maintaining the high security wall.

Encryption, decryption and signature verification tools_Yu Shanma's blog-CSDN blog_Checksum tool RSA encryption, decryption, signature, verification golang packaging https://blog.csdn.net/weixin_47560078/article/details/126042242

2. Architecture V1

Killing a chicken with a weak knife ? Let’s take this opportunity to review the forgotten technology again. Let’s briefly introduce the digital labeling process:

1. The service layer starts for the first time, generates Server-UUID, RSA Key Pair, and sends initialization information data to DRS. 
2. Nginx load balancing distributes multiple server requests to different DRS-Node.
3. DRS-Node saves the initialization information data to Mysql Master, Redis Master

The orange part is the log system, which has always recorded and collected data. For the construction of the ELK log system, you can refer to the blog written before.

Win10 builds ELK8.0.0 environment_Yu Shanma's blog-CSDN blog_xpack.enrollment download refer to the official download address. Elasticsearch configuration configuration file path.\elasticsearch-8.0.0\config\elasticsearch.yml# Host IPnetwork.host: 127.0.0.1# Port http.port: 9200# Disable downloading Geoipingest.geoip.downloader.enabled: false# Configure cross-domain http.cors.enabled: truehttp.cors.allow-origin: " https://blog.csdn.net/weixin_47560078/article/details/123201491

3. Related technology stacks

Go1.18.4, Gin framework, Gorm framework, Redis 7.0.5, Mysql 8.0, MongoDB 6.0.2, Nginx 1.22.1, RabbitMQ 3.11.3

4. Utils

1、UUID

import uuid "github.com/satori/go.uuid"

/* 生成全局唯一 uuid */

func GenerateUUID() string {
	return uuid.NewV4().String()
}

func GenerateServiceUUID() string {
	return os.Getenv("SERVICE") + GenerateUUID()
}

2、RSA

import (
	"crypto/rand"
	"crypto/rsa"
	"crypto/x509"
	"encoding/pem"
	"log"
	"os"
)


// 生成RSA私钥和公钥,保存到文件中

func GenerateRSAKey(bits int, privateFilePath string) []byte {
	// GenerateKey函数使用随机数据生成器random生成一对具有指定字位数的RSA密钥
	// Reader是一个全局、共享的密码用强随机数生成器
	privateKey, err := rsa.GenerateKey(rand.Reader, bits)
	if err != nil {
		log.Fatalln(err)
	}
	// 保存私钥
	// 通过x509标准将得到的ras私钥序列化为ASN.1 的 DER编码字符串
	X509PrivateKey := x509.MarshalPKCS1PrivateKey(privateKey)
	// 使用pem格式对x509输出的内容进行编码
	// 创建文件保存私钥
	//privateFile, err := os.Create("./files/private.pem")
	privateFile, err := os.Create(privateFilePath)
	if err != nil {
		log.Fatalln(err)
	}
	defer privateFile.Close()
	// 构建一个pem.Block结构体对象
	privateBlock := pem.Block{Type: "RSA Private Key", Bytes: X509PrivateKey}
	// 将数据保存到文件
	pem.Encode(privateFile, &privateBlock)

	// 保存公钥
	// 获取公钥的数据
	publicKey := privateKey.PublicKey
	// X509对公钥编码
	X509PublicKey, err := x509.MarshalPKIXPublicKey(&publicKey)
	if err != nil {
		log.Fatalln(err)
	}
	// pem格式编码

	// 创建一个pem.Block结构体对象
	publicBlock := pem.Block{Type: "RSA Public Key", Bytes: X509PublicKey}
	// 保存到文件
	return pem.EncodeToMemory(&publicBlock)
}

// RSA 公钥加密

func EncryptByPublicKey(plainText []byte, publicKeyPem []byte) []byte {
	// pem解码
	block, _ := pem.Decode(publicKeyPem)
	// x509解码
	publicKeyInterface, err := x509.ParsePKIXPublicKey(block.Bytes)
	if err != nil {
		log.Fatalln(err)
	}
	// 类型断言
	publicKey := publicKeyInterface.(*rsa.PublicKey)
	// 对明文进行加密
	cipherText, err := rsa.EncryptPKCS1v15(rand.Reader, publicKey, plainText)
	if err != nil {
		log.Fatalln(err)
	}
	// 返回密文
	return Base64Encode(cipherText)
}

// RSA 私钥解密

func DecryptByPrivateKey(cipherText []byte, privateKeyPath string) []byte {
	//打开文件
	file, err := os.Open(privateKeyPath)
	if err != nil {
		log.Fatalln(err)
	}
	defer file.Close()
	//获取文件内容
	info, _ := file.Stat()
	buf := make([]byte, info.Size())
	file.Read(buf)
	//pem解码
	block, _ := pem.Decode(buf)
	//X509解码
	privateKey, err := x509.ParsePKCS1PrivateKey(block.Bytes)
	if err != nil {
		log.Fatalln(err)
	}
	//对密文进行解密
	decode, err := Base64Decode(cipherText)
	if err != nil {
		log.Fatalln(err)
	}
	plainText, _ := rsa.DecryptPKCS1v15(rand.Reader, privateKey, decode)
	//返回明文
	return plainText
}

3、Base64

import "encoding/base64"

func Base64Encode(str []byte) []byte {
	return []byte(base64.StdEncoding.EncodeToString(str))
}

func Base64Decode(str []byte) ([]byte, error) {
	return base64.StdEncoding.DecodeString(string(str))
}

4、Http

import (
	"DynamicRSASystem/server/structs"
	"bytes"
	"encoding/json"
	"io/ioutil"
	"log"
	"net/http"
	"time"
)

func DoPostRequest(url string, data interface{}) structs.ResponseResult {

	var request *http.Request
	var err error

	// 序列化参数
	marshal, err := json.Marshal(data)
	if err != nil {
		log.Fatalln(err.Error())
	}

	// 请求超时时间
	client := http.Client{Timeout: 1 * time.Second}

	// 传参
	if data == nil {
		request, err = http.NewRequest(http.MethodPost, url, nil)
	} else {
		request, err = http.NewRequest(http.MethodPost, url, bytes.NewBuffer(marshal))
	}

	if err != nil {
		log.Fatalln(err.Error())
	}

	// header
	request.Header.Set("Content-Type", "application/json")

	// 执行请求
	response, err := client.Do(request)
	if err != nil {
		log.Fatalln(err.Error())
	}
	// 请求结果
	all, err := ioutil.ReadAll(response.Body)
	if err != nil {
		log.Fatalln(err.Error())
	}
	defer response.Body.Close()
	// 响应结果序列化
	var res structs.ResponseResult
	err = json.Unmarshal(all, &res)
	if err != nil {
		log.Fatalln(err.Error())
	}
	return res
}

5. Gorm connects to Msql

package database

import (
	"demo/sys"
	"fmt"
	"gorm.io/driver/mysql" // mysql 数据库驱动
	"gorm.io/gorm"         // 使用 gorm ,操作数据库的 orm 框架
	"gorm.io/gorm/logger"
	"log"
	"os"
)

//go 访问权限:
//变量名、函数名、常量名首字母大写,则可以被其他包访问,
//如果首字母小写,则只能在本包中使用。
//首字母大写为共有,首字母小写为私有。

var Db *gorm.DB

//数据库初始化
//init() 表示包初始化的时候执行的函数, 如果函数名写成 main() , 会在操作数据的时候报错。
//参考 https://github.com/go-sql-driver/mysql#dsn-data-source-name 获取详情。

func init() {

	// ----------------------- 日志设置 -----------------------
	newLogger := logger.New(
		log.New(os.Stdout, "\r\n", log.LstdFlags), // io writer
		logger.Config{
			SlowThreshold:             sys.SlowThreshold,             // Slow SQL threshold
			LogLevel:                  sys.LogLevel,                  // Log level
			IgnoreRecordNotFoundError: sys.IgnoreRecordNotFoundError, // Ignore ErrRecordNotFound error for logger
			Colorful:                  sys.Colorful,                  // Disable color
		},
	)
	// ----------------------- 连接数据库 -----------------------
	var err error
	Db, err = gorm.Open(mysql.New(mysql.Config{
		DSN:                       sys.DSN,                       // DSN data source name
		DefaultStringSize:         sys.DefaultStringSize,         // string 类型字段的默认长度
		DisableDatetimePrecision:  sys.DisableDatetimePrecision,  // 禁用 datetime 精度,MySQL 5.6 之前的数据库不支持
		DontSupportRenameIndex:    sys.DontSupportRenameIndex,    // 重命名索引时采用删除并新建的方式,MySQL 5.7 之前的数据库和 MariaDB 不支持重命名索引
		DontSupportRenameColumn:   sys.DontSupportRenameColumn,   // 用 `change` 重命名列,MySQL 8 之前的数据库和 MariaDB 不支持重命名列
		SkipInitializeWithVersion: sys.SkipInitializeWithVersion, // 根据当前 MySQL 版本自动配置
	}), &gorm.Config{
		Logger: newLogger,
	})
	if err != nil {
		fmt.Printf(sys.MySQLConnectErr, err)
	}
	if Db.Error != nil {
		fmt.Printf(sys.DataBaseErr, Db.Error)
	}

	// ----------------------- 连接池设置 -----------------------
	sqlDB, err := Db.DB()

	// SetMaxIdleConns 设置空闲连接池中连接的最大数量
	sqlDB.SetMaxIdleConns(sys.MaxIdleConns)

	// SetMaxOpenConns 设置打开数据库连接的最大数量。
	sqlDB.SetMaxOpenConns(sys.MaxOpenConns)

	// SetConnMaxLifetime 设置了连接可复用的最大时间。
	sqlDB.SetConnMaxLifetime(sys.ConnMaxLifetime)

	fmt.Println(sys.ConnectedMySQL)
}

// 分页封装

func Paginate(pageNum int,pageSize int) func(db *gorm.DB) *gorm.DB {
	return func (db *gorm.DB) *gorm.DB {
		if pageNum == 0 {
			pageNum = 1
		}
		switch {
		case pageSize > 100:
			pageSize = 100
		case pageSize <= 0:
			pageSize = 10
		}
		offset := (pageNum - 1) * pageSize
		return db.Offset(offset).Limit(pageSize)
	}
}

6. Connecting to Redis and operating encapsulation

package redis

import (
	"demo/sys"
	"fmt"
	"github.com/go-redis/redis"
	"log"
	"time"
)

var client *redis.Client

/*初始化连接*/
func init() {
	ConnectRedis()
}

/* 连接 redis 服务端 */
func ConnectRedis() {
	client = redis.NewClient(&redis.Options{
		Addr:     sys.RedisAddr,     // 默认连接地址为 localhost:6379
		DB:       sys.RedisDbIndex,  // redis 默认有 0-15 共 16 个数据库,这里设置操作索引为 index 的数据库
		Password: sys.RedisPassword, // 默认无密码
	})

	pong, err := client.Ping().Result()

	if err != nil {
		log.Fatal(err)
	}

	if pong != "PONG" {
		log.Fatal(sys.FailToConnectRedis)
	} else {
		fmt.Println(sys.RedisConnectionIsSuccessful)
	}
}



/* === string类型数据操作 === */
/* redis命令:set key val */
func Set(key, val string) string {
	//有效期为0表示不设置有效期,非0表示经过该时间后键值对失效
	result, err := client.Set(key, val, 0).Result()

	if err != nil {
		log.Fatal(err)
	}
	return result
}

/* redis命令:get key */
func Get(key string) string {
	val, err := client.Get(key).Result()

	if err != nil {
		log.Fatal(err)
	}
	fmt.Println(val)
	return val
}

/* redis命令:mset key1 val1 key2 val2 key3 val3 ... */
func Mset(key1, val1, key2, val2, key3, val3 string) string {
	//以下三种方式都可以
	//result,err := client.MSet(key1,val1,key2,val2,key3,val3).Result()
	//result,err := client.MSet([]string{key1,val1,key2,val2,key3,val3}).Result()
	result, err := client.MSet(map[string]interface{}{key1: val1, key2: val2, key3: val3}).Result()

	if err != nil {
		log.Fatal(err)
	}
	fmt.Println(result)
	return result
}

/* redis命令:mget key1 key2 key3 ... */
func Mget(key1, key2, key3 string) []interface{} {
	vals, err := client.MGet(key1, key2, key3).Result()

	if err != nil {
		log.Fatal(err)
	}

	for k, v := range vals {
		fmt.Printf("k = %v v = %s\n", k, v)
	}

	return vals
}

/* redis命令:del key1 key2 key3 ... */
func Del(key1 ...string) int64 {
	result, err := client.Del(key1...).Result()

	if err != nil {
		log.Fatal(err)
	}
	fmt.Println(result)
	return result
}

/* redis命令:getrange key start end */
func Getrange(key string, start, end int64) string {
	val, err := client.GetRange(key, start, end).Result()

	if err != nil {
		log.Fatal(err)
	}
	fmt.Println(val)
	return val
}

/* redis命令:strlen key */
func Strlen(key string) int64 {
	len, err := client.StrLen(key).Result()

	if err != nil {
		log.Fatal(err)
	}
	fmt.Println(len)
	return len
}

/* redis命令:setex key time val */
func Setex(key, val string, expire int) string {
	//time.Duration其实也是int64,不过是int64的别名罢了,但这里如果expire使用int64也无法与time.Second运算,
	//因为int64和Duration虽然本质一样,但表面上属于不同类型,go语言中不同类型是无法做运算的
	result, err := client.Set(key, val, time.Duration(expire)*time.Second).Result()

	if err != nil {
		log.Fatal(err)
	}
	fmt.Println(result)
	return result
}

/* redis命令:append key val */
func Append(key, val string) int64 {
	//将val插入key对应值的末尾,并返回新串长度
	len, err := client.Append(key, val).Result()

	if err != nil {
		log.Fatal(err)
	}
	fmt.Println(len)
	return len
}

/* redis命令:exists key */
func Exists(key string) int64 {
	// 返回1表示存在,0表示不存在
	isExists, err := client.Exists(key).Result()

	if err != nil {
		log.Fatal(err)
	}
	fmt.Println(isExists)
	return isExists
}

/* === hash类型数据操作 === */
/* redis命令:hset hashTable key val */
func Hset(hashTable, key, val string) bool {
	isSetSuccessful, err := client.HSet(hashTable, key, val).Result()

	if err != nil {
		log.Fatal(err)
	}
	//如果键存在这返回false,如果键不存在则返回true
	fmt.Println(isSetSuccessful)
	return isSetSuccessful
}

/* redis命令:hget hashTable key */
func Hget(hashTable, key string) string {
	val, err := client.HGet(hashTable, key).Result()

	if err != nil {
		log.Fatal(err)
	}
	fmt.Println(val)
	return val
}

/* redis命令:hmset hashTable map[string]interface{} */
func Hmset(hashTable string, fields map[string]interface{}) string {
	res, err := client.HMSet(hashTable, fields).Result()

	if err != nil {
		log.Fatal(err)
	}
	return res
}

/* redis命令:hmget hashTable key1 key2 key3 ... */
func Hmget(hashTable, key1, key2, key3 string) []interface{} {
	vals, err := client.HMGet(hashTable, key1, key2, key3).Result()

	if err != nil {
		log.Fatal(err)
	}

	for k, v := range vals {
		fmt.Printf("k = %v v = %s\n", k, v)
	}
	return vals
}

/* redis命令:hdel hashTable key1 key2 key3 ... */
func Hdel(hashTable, key1 string) int64 {
	//返回1表示删除成功,返回0表示删除失败
	//只要至少有一个被删除则返回1(不存在的键不管),一个都没删除则返回0(不存在的则也算没删除)
	res, err := client.Del(hashTable, key1).Result()

	if err != nil {
		log.Fatal(err)
	}
	fmt.Println(res)
	return res
}

/* redis命令:hgetall hashTable */
func Hgetall(hashTable string) map[string]string {
	vals, err := client.HGetAll(hashTable).Result()

	if err != nil {
		log.Fatal(err)
	}

	for k, v := range vals {
		fmt.Printf("k = %v v = %s\n", k, v)
	}

	return vals
}

/* redis命令:hexists hashTable key */
func Hexists(hashTable, key string) bool {
	isExists, err := client.HExists(hashTable, key).Result()

	if err != nil {
		log.Fatal(err)
	}
	fmt.Println(isExists)
	return isExists
}

/* redis命令:hlen hashTable */
func Hlen(hashTable string) int64 {
	len, err := client.HLen(hashTable).Result()

	if err != nil {
		log.Fatal(err)
	}
	fmt.Println(len)
	return len
}

/* redis命令:hkeys hashTable */
func Hkeys(hashTable string) []string {
	keys, err := client.HKeys(hashTable).Result()

	if err != nil {
		log.Fatal(err)
	}

	for k, v := range keys {
		fmt.Printf("k = %v v = %s\n", k, v)
	}
	return keys
}

/* redis命令:hvals hashTable */
func Hvals(hashTable string) []string {
	vals, err := client.HVals(hashTable).Result()

	if err != nil {
		log.Fatal(err)
	}

	for k, v := range vals {
		fmt.Printf("k = %v v = %s\n", k, v)
	}

	return vals
}

7. Connect to MongoDB

package mongodb

import (
	"context"
	"demo/sys"
	"fmt"
	"go.mongodb.org/mongo-driver/mongo/writeconcern"
	"log"

	"go.mongodb.org/mongo-driver/mongo"
	"go.mongodb.org/mongo-driver/mongo/options"
)

var MongoDB *mongo.Client

func init() {
	ConnectMongoDB()
}

func ConnectMongoDB() {

	//在连接时指定单节点连接而不是集群,否则会报错
	//server selection error: server selection timeout, current topology: { Type: Unknown, Servers: [{ Addr: localhost:27017, Type: Unknown, Last error: connection() error occured during connection handshake: dial tcp [::1]:27017: connect: connection refused }, ] }
	//connect=direct
	//clientOpts := options.Client().ApplyURI("mongodb://user:password@ip:port/?connect=direct&authSource=admin&authMechanism=SCRAM-SHA-1")

	// 设置客户端连接配置,指定 writeConcern 为 {w: 0} 可以达到最高吞吐量
	clientOptions := options.Client().ApplyURI(sys.MongoDBUrl).SetWriteConcern(writeconcern.New(writeconcern.W(0)))

	// 连接到MongoDB
	client, err := mongo.Connect(context.TODO(), clientOptions)
	if err != nil {
		log.Fatal(err)
	}

	// 检查连接
	err = client.Ping(context.TODO(), nil)
	if err != nil {
		log.Fatal(err)
	}
	fmt.Println(sys.ConnectedMongoDB)
	MongoDB = client
}

8. System global static variables

package sys

import (
	"gorm.io/gorm/logger"
	"time"
)

// mongoDb 封装
const MongoDBUrl string = "mongodb://user:password@ip:port/?connect=direct&authSource=admin&authMechanism=SCRAM-SHA-1"

const ConnectedMongoDB string = "Connected to MongoDB"

// redis 封装

const RedisAddr string = "127.0.0.1:6379" // 内网 redis-server

const RedisDbIndex int = 0 // 不同数据服务节点应使用不同的数据库

const RedisPassword string = ""

const RedisHashTableName string = "" // 不同数据服务节点应使用不同的 hashTable

const FailToConnectRedis string = "fail to connect redis server"

const RedisConnectionIsSuccessful = "The client has successfully connected to the Redis server"

// mysql 封装

//----------------------- 日志设置 -----------------------

const SlowThreshold = time.Second
const LogLevel = logger.Silent
const IgnoreRecordNotFoundError = true
const Colorful = false

// ----------------------- 连接数据库 -----------------------

const ConnectedMySQL string = "Connected to ConnectedMySQL"

// 考虑到网络信号问题,timeout 不应设置太短时间

const DSN string = "root:root@tcp(127.0.0.1:3306)/db?charset=utf8&parseTime=True&loc=Local&timeout=1000ms"

const DefaultStringSize = 256

const DisableDatetimePrecision = true
const DontSupportRenameIndex = true
const DontSupportRenameColumn = true
const SkipInitializeWithVersion = true

const MaxIdleConns = 10

const MaxOpenConns = 100

const ConnMaxLifetime = time.Hour

const MySQLConnectErr string = "mysql connect error %v"

const DataBaseErr string = "database error %v"

9. Database design

CREATE TABLE `DrsServiceInitInfos` (
	`id` VARCHAR(50) NOT NULL COLLATE 'utf8mb4_0900_ai_ci',
	`serviceId` VARCHAR(50) NOT NULL COMMENT '服务id' COLLATE 'utf8mb4_0900_ai_ci',
	`publicKey` VARCHAR(256) NOT NULL COMMENT '公钥' COLLATE 'utf8mb4_0900_ai_ci',
	`expiredTime` VARCHAR(20) NOT NULL COMMENT '过期时间' COLLATE 'utf8mb4_0900_ai_ci',
	`currentStatus` VARCHAR(10) NOT NULL DEFAULT 'normal' COMMENT '当前状态' COLLATE 'utf8mb4_0900_ai_ci',
	`createdAt` VARCHAR(20) NOT NULL COMMENT '创建时间' COLLATE 'utf8mb4_0900_ai_ci',
	`updatedAt` VARCHAR(20) NULL DEFAULT NULL COMMENT '更新时间' COLLATE 'utf8mb4_0900_ai_ci',
	`createdUser` VARCHAR(20) NOT NULL COLLATE 'utf8mb4_0900_ai_ci',
	`updatedUser` VARCHAR(20) NULL DEFAULT NULL COMMENT '操作人id' COLLATE 'utf8mb4_0900_ai_ci',
	PRIMARY KEY (`id`) USING BTREE
)
COMMENT='初始化信息'
COLLATE='utf8mb4_0900_ai_ci'
ENGINE=InnoDB
;

10. Gin MVC framework construction

Simply build Gin + GORM MVC framework_Yu Shanma's blog-CSDN blog_gin mvc framework reference GORM guide directory structure controller: responsible for request forwarding, accepting parameters from the page, passing them to the Model for processing, receiving the return value, and then passing it to page. database: connect to the database through gorm. models: Corresponds to the addition, deletion, query and modification of data tables. router: handles routing. templates: HTML templates. Define routing package routerimport (. "gin-mvc/controller" "github.com/gin-gonic/gin")/*InitRouter path. https://blog.csdn.net/weixin_47560078/article/details/119245691?spm= 1001.2014.3001.5501 refer to this article.

Architecture changes

  • Added JWT protection API interface
  • Added email sending event report
  • Added Rabbit MQ cache queue
  • Rule constraints

middleware

1. Cross-domain middleware

package middleware

/**
 * 跨域中间件
 *
 * @author yushanma
 */


import (
"github.com/gin-contrib/cors"
"github.com/gin-gonic/gin"
"time"
)

// 跨域请求

func Cors() gin.HandlerFunc {
	handlerFunc := cors.New(cors.Config{
		AllowMethods:     []string{"*"},
		AllowHeaders:     []string{"content-type", "token", "fileType", "size", "digest"}, //此处设置非默认之外的请求头(自定义请求头),否则会出现跨域问题
		AllowAllOrigins:  true,
		AllowCredentials: true,
		MaxAge:           24 * time.Hour,
		ExposeHeaders:    []string{"*"},
	})
	return handlerFunc
}

// gin 上下文配置 cors

func CORSMiddleware() gin.HandlerFunc {
	return func(c *gin.Context) {
		c.Writer.Header().Set("Access-Control-Allow-Origin", "*")
		c.Writer.Header().Set("Access-Control-Allow-Credentials", "true")
		c.Writer.Header().Set("Access-Control-Allow-Headers", "Content-Type, Content-Length, Accept-Encoding, X-CSRF-Token, Authorization, accept, origin, Cache-Control, X-Requested-With")
		c.Writer.Header().Set("Access-Control-Allow-Methods", "POST, GET, PUT, HEAD, OPTIONS")

		if c.Request.Method == "OPTIONS" {
			c.AbortWithStatus(204)
			return
		}

		c.Next()
	}
}

//nginx 跨域

//server {
//#以上省略
//add_header 'Access-Control-Allow-Origin' '*';
//add_header 'Access-Control-Allow-Headers' 'X-Pagination-Current-Page,Content-Type';
//add_header 'Access-Control-Allow-Methods' 'PUT,GET,POST,HEAD,DELETE';
//add_header 'Access-Control-Expose-Headers' 'X-Pagination-Current-Page,Content-Type';
//#以下省略
//}
//Allow-Headers "Accept","Accept-Encoding","Host","Origin","Referer","User-Agent",

2. JWT middleware

package middleware

import (
	"DynamicRSASystem/sys"
	"DynamicRSASystem/utils"
	"github.com/gin-gonic/gin"
	"log"
	"net/http"
)

/**
 * jwt
 *
 * @author yushanma
 * @since 2022/11/9 11:48
 */

// 定义一个JWTAuth的中间件,用于接口鉴权
// 1、获取 header 中的 token
// 2、校验解析 token
// 3、负载信息写入上下文 gin.Context
// 4、调用后续处理函数,如日志中间件

func JWTAuth() gin.HandlerFunc {
	return func(c *gin.Context) {
		// 通过 http header 中的 token 解析来认证
		token := c.Request.Header.Get("token")
		if token == "" {
			c.JSON(http.StatusOK, gin.H{
				"code": -1,
				"msg":  sys.NoneTokenErr,
				"data": nil,
			})
			c.Abort()
			return
		}

		log.Println("token length", len(token))

		// 初始化一个JWT对象实例,并根据结构体方法来解析token
		j := utils.NewJWT()
		// 解析token中包含的相关信息(有效载荷)
		claims, err := j.ParserToken(token)

		if err != nil {
			// token过期
			if err.Error() == sys.TokenExpired {
				c.JSON(http.StatusOK, gin.H{
					"code": -1,
					"msg":  sys.ExpiredTokenErr,
					"data": nil,
				})
				c.Abort()
				return
			}
			// 其他错误
			c.JSON(http.StatusOK, gin.H{
				"code": -1,
				"msg":  err.Error(),
				"data": nil,
			})
			c.Abort()
			return
		}

		// 将解析后的有效载荷 claims 重新写入 gin.Context 引用对象中
		c.Set("claims", claims)
		// 调用后续处理函数
		c.Next()
	}
}

3. Log middleware

package middleware

import (
	"DynamicRSASystem/mongo"
	"DynamicRSASystem/utils"
	"github.com/gin-gonic/gin"
)

/**
 * log 记录
 *
 * @author yushanma
 * @since 2022/11/9 11:49
 */

// 日志中间件,记录用户访问信息
// 1、检查并解析 jwt 信息
// 2、获取请求 header 信息
// 3、写入数据库

func SystemLogRecord() gin.HandlerFunc {
	return func(c *gin.Context) {
		// 检查并解析 jwt 信息
		value, exists := c.Get("claims")
		var claims *utils.CustomClaims
		if exists {
			claims = value.(*utils.CustomClaims)
		} else {
			claims = &utils.CustomClaims{
				UserCode: "null",
				UserRole: "visitors",
			}
		}
		// 获取请求 header 信息
		hostIp := c.RemoteIP()

		// 写入数据库
		mongo.InsertServerlog(hostIp, claims.UserCode, claims.UserRole, c.FullPath(), c.Request.Header.Get("Referer"), c.Request.Header.Get("User-Agent"))
	}
}

JWT Util package

package utils

import (
	"github.com/dgrijalva/jwt-go"
	"DynamicRSASystem/sys"
	"fmt"
)

/**
 * jwt 鉴权
 *
 * @author yushanma
 * @since 2022/11/9 14:04
 */

// 定义一个jwt对象

type JWT struct {
	// 声明签名信息
	SigningKey []byte
}

// 初始化jwt对象

func NewJWT() *JWT {
	return &JWT{
		[]byte(sys.SigningKey),
	}
}

// 自定义有效载荷(这里采用自定义的 UserCode 和 UserRole 作为有效载荷的一部分)

type CustomClaims struct {
	UserCode string `json:"userCode"`
	UserRole string `json:"userRole"`
	// StandardClaims结构体实现了Claims接口(Valid()函数)
	jwt.StandardClaims
}

// 调用jwt-go库生成token
// 指定编码的算法为jwt.SigningMethodHS512

func (j *JWT) CreateToken(claims CustomClaims) (string, error) {
	// https://gowalker.org/github.com/dgrijalva/jwt-go#Token
	// 返回一个token的结构体指针
	token := jwt.NewWithClaims(jwt.SigningMethodHS512, claims)
	return token.SignedString(j.SigningKey)
}

// token解码

func (j *JWT) ParserToken(tokenString string) (*CustomClaims, error) {
	// https://gowalker.org/github.com/dgrijalva/jwt-go#ParseWithClaims
	// 输入用户自定义的Claims结构体对象,token,以及自定义函数来解析token字符串为jwt的Token结构体指针
	// Keyfunc是匿名函数类型: type Keyfunc func(*Token) (interface{}, error)
	// func ParseWithClaims(tokenString string, claims Claims, keyFunc Keyfunc) (*Token, error) {}
	token, err := jwt.ParseWithClaims(tokenString, &CustomClaims{}, func(token *jwt.Token) (interface{}, error) {
		return j.SigningKey, nil
	})

	if err != nil {
		// https://gowalker.org/github.com/dgrijalva/jwt-go#ValidationError
		// jwt.ValidationError 是一个无效token的错误结构
		if ve, ok := err.(*jwt.ValidationError); ok {
			// ValidationErrorMalformed是一个uint常量,表示token不可用
			if ve.Errors&jwt.ValidationErrorMalformed != 0 {
				return nil, fmt.Errorf(sys.TokenDisabled)
				// ValidationErrorExpired表示Token过期
			} else if ve.Errors&jwt.ValidationErrorExpired != 0 {
				return nil, fmt.Errorf(sys.TokenExpired)
				// ValidationErrorNotValidYet表示无效token
			} else if ve.Errors&jwt.ValidationErrorNotValidYet != 0 {
				return nil, fmt.Errorf(sys.TokenInvalid)
			} else {
				return nil, fmt.Errorf(sys.TokenDisabled)
			}

		}
	}

	// 将token中的claims信息解析出来并断言成用户自定义的有效载荷结构
	if claims, ok := token.Claims.(*CustomClaims); ok && token.Valid {
		return claims, nil
	}

	return nil, fmt.Errorf(sys.TokenInvalid)

}

Mail tool package

package utils

import (
	"DynamicRSASystem/sys"
	"DynamicRSASystem/types"
	"crypto/tls"
	"regexp"

	"github.com/go-gomail/gomail"
)

/**
 * email 代理
 *
 * @author yushanma
 * @since 2022/11/9 14:35
 */

func VerifyEmailFormat(email string) bool {
	pattern := `\w+([-+.]\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*` //匹配电子邮箱
	reg := regexp.MustCompile(pattern)
	return reg.MatchString(email)
}

//发送邮件,content 为发送内容,to 为收件人

func SendEmail(msg types.EmailMsgStruct) {
	m := gomail.NewMessage()
	m.SetHeader("From", sys.EmailName) // 发件人
	m.SetHeader("To", msg.To)              // 收件人
	m.SetHeader("Subject", msg.Subject)    // 邮件标题
	m.SetBody("text/html", msg.Content)    // 邮件内容
	//m.SetAddressHeader("Cc", sys.EmailName, sys.EmailUserName) // 抄送

	d := gomail.NewDialer(sys.EmailDialer, sys.EmailTLlsPort, sys.EmailName, sys.EmailToken) // 代理,端口,用户,授权码
	d.TLSConfig = &tls.Config{InsecureSkipVerify: true}                                      // TLS 配置
	// 发送
	if err := d.DialAndSend(m); err != nil {
		panic(err)
	}
}
package utils

import (
	"math/rand"
	"time"
)

/**
 * 验证码
 *
 * @author yushanma
 * @since 2022/11/9 14:43
 */


func GetVerifyEmailCode() string {
	var verifyEmailCode []byte
	// 验证码组成库
	charArray := []byte{
		'1', '2', '3', '4', '5', '6', '7', '8', '9', '0',
		'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j',
		'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't',
		'u', 'v', 'w', 'x', 'y', 'z',
		'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J',
		'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T',
		'U', 'V', 'W', 'X', 'Y', 'Z',
	}
	// 时间戳作为随机种子
	rand.Seed(time.Now().Unix())
	// 生成6为验证码
	for i := 0; i < 6; i++ {
		verifyEmailCode = append(verifyEmailCode, charArray[rand.Intn(len(charArray))])
	}
	return string(verifyEmailCode)
}

RabbitMQ environment construction and packaging

Refer to previous blog

Docker image pull docker pull image https://blog.csdn.net/weixin_47560078/article/details/126189386?spm=1001.2014.3001.5501 or

Handwritten distributed object storage system from 0 to 1 - Initial construction of 02 distributed architecture_Yu Shanma's blog - CSDN blog Handwritten distributed object storage system from 0 to 1 https://blog.csdn.net/weixin_47560078/article/ details/120829103?spm=1001.2014.3001.5501

Guess you like

Origin blog.csdn.net/weixin_47560078/article/details/127576152