Merge branch 'refs/heads/feat-sync-secret'
# Conflicts: # main.go
This commit is contained in:
commit
3fc24cbc1a
56
main.go
56
main.go
|
|
@ -62,8 +62,7 @@ func init() {
|
|||
os.MkdirAll(proto.CID_BASE_DIR+"workspace", os.ModePerm)
|
||||
//读取配置文件
|
||||
//文件地址/home/videoplayer/vp.conf
|
||||
//configPath := "/home/videoplayer/vp.conf"
|
||||
configPath := "/etc/vp-app/vp.conf"
|
||||
configPath := "/home/videoplayer/vp.conf"
|
||||
//读取配置文件
|
||||
err := proto.ReadConfig(configPath)
|
||||
if err != nil {
|
||||
|
|
@ -106,17 +105,17 @@ func JWTAuthMiddleware() gin.HandlerFunc {
|
|||
if tokenString == "" {
|
||||
tokenString = c.Query("token")
|
||||
}
|
||||
for k, _ := range proto.Url_map {
|
||||
if strings.Contains(c.Request.URL.Path, k) {
|
||||
log.Println("need not check token:", c.Request.URL.Path)
|
||||
//如果请求为login或register,则不需要验证token
|
||||
//for k, _ := range proto.Url_map {
|
||||
// if strings.Contains(c.Request.URL.Path, k) {
|
||||
// c.Next()
|
||||
// return
|
||||
// }
|
||||
//}
|
||||
if proto.Url_map[c.Request.URL.Path] == true { //查看是否在不需要token的url中
|
||||
c.Next()
|
||||
return
|
||||
}
|
||||
}
|
||||
//if proto.Url_map[c.Request.URL.Path] == true { //查看是否在不需要token的url中
|
||||
// c.Next()
|
||||
// return
|
||||
//}
|
||||
if tokenString == "" {
|
||||
c.AbortWithStatusJSON(http.StatusOK, gin.H{"message": "unauthorized", "error": "token is empty", "code": proto.TokenIsNull})
|
||||
return
|
||||
|
|
@ -147,34 +146,22 @@ func JWTAuthMiddleware() gin.HandlerFunc {
|
|||
return
|
||||
}
|
||||
|
||||
proto.SigningKeyRWLock.RLock() //加读锁
|
||||
// 使用加密secret 解析 JWT 令牌
|
||||
//token, err := jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) {
|
||||
// return proto.SigningKey, nil
|
||||
//})
|
||||
token, err := jwt.Parse(tokenString, func(t *jwt.Token) (interface{}, error) {
|
||||
// 验证签名算法
|
||||
if _, ok := t.Method.(*jwt.SigningMethodHMAC); !ok {
|
||||
return nil, jwt.ErrSignatureInvalid
|
||||
}
|
||||
token, err := jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) {
|
||||
return proto.SigningKey, nil
|
||||
})
|
||||
// 错误处理
|
||||
if err != nil {
|
||||
var ve *jwt.ValidationError
|
||||
if errors.As(err, &ve) {
|
||||
switch {
|
||||
case ve.Errors&jwt.ValidationErrorMalformed != 0:
|
||||
c.AbortWithStatusJSON(http.StatusOK, gin.H{"error": "Malformed token:" + err.Error(), "code": proto.TokenInvalid})
|
||||
case ve.Errors&jwt.ValidationErrorExpired != 0:
|
||||
c.AbortWithStatusJSON(http.StatusOK, gin.H{"error": "Token expired:" + err.Error(), "code": proto.TokenExpired})
|
||||
case ve.Errors&jwt.ValidationErrorNotValidYet != 0:
|
||||
c.AbortWithStatusJSON(http.StatusOK, gin.H{"error": "Token not active yet:" + err.Error(), "code": proto.TokenInvalid})
|
||||
default:
|
||||
c.AbortWithStatusJSON(http.StatusOK, gin.H{"error": "Invalid token:" + err.Error(), "code": proto.TokenInvalid})
|
||||
}
|
||||
proto.SigningKeyRWLock.RUnlock()
|
||||
|
||||
// 验证令牌
|
||||
if err != nil || !token.Valid {
|
||||
c.AbortWithStatusJSON(http.StatusOK, gin.H{
|
||||
"message": "NOT_LOGIN",
|
||||
"error": "Invalid token",
|
||||
"code": proto.TokenExpired,
|
||||
})
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// 将用户信息添加到上下文中
|
||||
id := token.Claims.(jwt.MapClaims)["id"]
|
||||
|
|
@ -187,9 +174,9 @@ func JWTAuthMiddleware() gin.HandlerFunc {
|
|||
c.AbortWithStatusJSON(http.StatusOK, gin.H{"message": "unauthorized", "error": "no function permission", "code": proto.NoPermission})
|
||||
return
|
||||
}
|
||||
|
||||
// 继续处理请求
|
||||
c.Next()
|
||||
//log.Println("JWT token is valid, user ID:", token.Claims.(jwt.MapClaims)["id"], " path:", c.Request.URL.Path)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -203,6 +190,7 @@ func myTask() {
|
|||
//其它定时任务-通用
|
||||
RunGeneralCron()
|
||||
service.ShellWillRunFromServer()
|
||||
service.SyncTokenSecretFromUserCenter()
|
||||
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ import (
|
|||
"gorm.io/gorm"
|
||||
"log"
|
||||
"os"
|
||||
"sync"
|
||||
)
|
||||
|
||||
var Config ConfigStruct
|
||||
|
|
@ -13,6 +14,13 @@ var SigningKey = []byte{}
|
|||
var Url_map = map[string]bool{"/login": true, "/register": true, "/uuid": true, "/gqr": true, "/cid/callback": true, "/tool/monitor": true, "/user/sync": true, "/tool/file/": true, "/user/reset": true} // 不需要token验证的url
|
||||
var Per_menu_map = map[string]int{"/video/": 1, "/device/": 2, "/cid/": 3}
|
||||
var File_Type = map[string]int{"im": 1, "avatar": 2, "file": 3, "config": 4} // 文件类型
|
||||
|
||||
// 配置读写锁
|
||||
var ConfigRWLock = &sync.RWMutex{}
|
||||
var SigningKeyRWLock = &sync.RWMutex{}
|
||||
|
||||
var SyncSecretReqLog int64
|
||||
|
||||
const (
|
||||
MYSQL_USER = "video_t2"
|
||||
MYSQL_DB = "video_t2"
|
||||
|
|
@ -86,6 +94,30 @@ type ConfigStruct struct {
|
|||
MONITOR_SERVER_TOKEN string `json:"monitor_server_token"` // 监控服务器token,用于状态监控及邮件通知
|
||||
}
|
||||
|
||||
func WriteConfigToFile() {
|
||||
//系统是linux、macos还是windows
|
||||
var configPath string
|
||||
if os.Getenv("OS") == "Windows_NT" {
|
||||
configPath = "D:/Code/videoplayer/vp.conf"
|
||||
} else if os.Getenv("OS") == "linux" {
|
||||
//文件地址/home/saw-ai/saw-ai.conf
|
||||
configPath = "/home/videoplayer/vp.conf"
|
||||
} else {
|
||||
configPath = "/home/videoplayer/vp.conf"
|
||||
}
|
||||
configData, err := json.MarshalIndent(Config, "", " ")
|
||||
if err != nil {
|
||||
log.Println("WriteConfigToFile json marshal error:", err)
|
||||
return
|
||||
}
|
||||
err = os.WriteFile(configPath, configData, 0644)
|
||||
if err != nil {
|
||||
log.Println("WriteConfigToFile write file error:", err)
|
||||
return
|
||||
}
|
||||
log.Println("WriteConfigToFile write config to file success")
|
||||
}
|
||||
|
||||
// 读取配置文件
|
||||
func ReadConfig(path string) error {
|
||||
//查看配置文件是否存在,不存在则创建
|
||||
|
|
|
|||
|
|
@ -117,3 +117,29 @@ type ResponseOAuth struct {
|
|||
Email string `json:"email" form:"email"`
|
||||
Token string `json:"token" form:"token"`
|
||||
}
|
||||
|
||||
type SecretSyncSettings struct {
|
||||
Prev string `json:"prev"` // 前一个secret
|
||||
Curr string `json:"curr"` // 当前的secret
|
||||
Next string `json:"next"` // 下一个secret
|
||||
CurrExpectedExpiration int64 `json:"curr_expected_expiration"` // 当前密钥的预期过期时间戳
|
||||
PrevEndTimestamp int64 `json:"prev_end_timestamp"` // 前一个secret的结束时间戳
|
||||
CurrStartTimestamp int64 `json:"curr_start_timestamp"` // 当前secret的开始时间戳
|
||||
NextStartTimestamp int64 `json:"next_start_timestamp"` // 下一个secret的开始时间戳
|
||||
}
|
||||
|
||||
type SyncSystemConfigRequest struct {
|
||||
//时间戳
|
||||
Timestamp int64 `json:"timestamp" form:"timestamp"` // 时间戳
|
||||
//设备标识
|
||||
DeviceApp string `json:"device_app" form:"device_app"` // 设备标识
|
||||
//加密信息
|
||||
Sign string `json:"sign" form:"sign"` // 加密信息,app的secret加密后的值
|
||||
SecretKeyMd5 string `json:"secret_key_md5" form:"secret_key_md5"` // 密钥的MD5值,用于验证
|
||||
}
|
||||
|
||||
type RequestSyncSecretResp struct {
|
||||
Code int `json:"code"`
|
||||
Message string `json:"message"`
|
||||
Data string `json:"data"`
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,9 +1,12 @@
|
|||
package service
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"log"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"time"
|
||||
"videoplayer/proto"
|
||||
"videoplayer/worker"
|
||||
|
|
@ -184,3 +187,133 @@ func DelMonitorDeviceListWithStatus(userId int, deviceReq []proto.GetMonitorDevi
|
|||
}
|
||||
return delDevices, err
|
||||
}
|
||||
|
||||
// 更新token密钥
|
||||
func SyncTokenSecretFromUserCenter() {
|
||||
secretSettings, err := GetTokenSecretFromUserCenter()
|
||||
//写入redis
|
||||
secretSettingsBytes, err2 := json.Marshal(secretSettings)
|
||||
if err2 != nil {
|
||||
log.Println("SyncTokenSecretFromUserCenter json marshal error:", err2)
|
||||
return
|
||||
}
|
||||
if proto.SyncSecretReqLog%100 == 0 {
|
||||
log.Println("SyncTokenSecretFromUserCenter req data:", string(secretSettingsBytes))
|
||||
}
|
||||
worker.SetRedis("secret_sync_settings", string(secretSettingsBytes)) //将密钥信息存入redis
|
||||
|
||||
if err != nil {
|
||||
log.Println("SyncTokenSecretFromUserCenter error:", err)
|
||||
return
|
||||
}
|
||||
if secretSettings.Curr != "" && secretSettings.Curr != proto.TOKEN_SECRET && secretSettings.Next == "" { //如果当前密钥不为空且不等于配置文件中的密钥,并且下一个密钥为空,则需要更新配置文件中的密钥
|
||||
log.Printf("SyncTokenSecretFromUserCenter current secret is not equal to config secret, current: %s, config: %s\n", secretSettings.Curr, proto.TOKEN_SECRET)
|
||||
//如果当前密钥与配置文件中的密钥不一致,则需要更新配置文件中的密钥
|
||||
proto.SigningKeyRWLock.Lock()
|
||||
proto.SigningKey = []byte(secretSettings.Curr)
|
||||
proto.Config.TOKEN_SECRET = secretSettings.Curr
|
||||
proto.SigningKeyRWLock.Unlock()
|
||||
//配置写回文件
|
||||
go proto.WriteConfigToFile()
|
||||
log.Println("SyncTokenSecretFromUserCenter current secret updated successfully")
|
||||
}
|
||||
|
||||
if secretSettings.Next == "" {
|
||||
log.Println("SyncTokenSecretFromUserCenter secret is empty")
|
||||
return
|
||||
} else if proto.SyncSecretReqLog%100 == 0 {
|
||||
go SetNextSecretToCurrent(*secretSettings) //异步设置下一个密钥为当前密钥
|
||||
}
|
||||
|
||||
proto.SyncSecretReqLog++ //记录同步密钥请求次数
|
||||
}
|
||||
|
||||
func SetNextSecretToCurrent(secret_copy proto.SecretSyncSettings) {
|
||||
var secret_sync_settings proto.SecretSyncSettings
|
||||
redisKey := "secret_sync_settings"
|
||||
settingsStr := worker.GetRedis(redisKey)
|
||||
err := json.Unmarshal([]byte(settingsStr), &secret_sync_settings)
|
||||
if err != nil {
|
||||
log.Println("Error decoding secret sync settings:", err)
|
||||
} else {
|
||||
//如果当前密钥的下一个密钥与传入的密钥不一致,则不进行设置
|
||||
if secret_copy.Next != secret_sync_settings.Next {
|
||||
return
|
||||
}
|
||||
//获取需要等待时间
|
||||
waitTime := secret_sync_settings.NextStartTimestamp - worker.GetCurrentTimestamp()
|
||||
if waitTime > 0 {
|
||||
log.Printf("Waiting for %d seconds before setting the next secret as current secret\n", waitTime)
|
||||
time.Sleep(time.Duration(waitTime) * time.Second) //等待时间
|
||||
} else {
|
||||
log.Println("No need to wait, setting the next secret as current secret immediately")
|
||||
}
|
||||
//设置下一个密钥为当前密钥
|
||||
secret_sync_settings.Prev = secret_sync_settings.Curr
|
||||
secret_sync_settings.PrevEndTimestamp = worker.GetCurrentTimestamp()
|
||||
secret_sync_settings.Curr = secret_sync_settings.Next
|
||||
secret_sync_settings.Next = ""
|
||||
secret_sync_settings.CurrStartTimestamp = secret_sync_settings.PrevEndTimestamp
|
||||
|
||||
//设置当前程序的密钥
|
||||
//获取写锁
|
||||
proto.SigningKeyRWLock.Lock()
|
||||
defer proto.SigningKeyRWLock.Unlock()
|
||||
proto.SigningKey = []byte(secret_sync_settings.Curr)
|
||||
|
||||
proto.Config.TOKEN_SECRET = secret_sync_settings.Curr
|
||||
//配置写回文件
|
||||
go proto.WriteConfigToFile()
|
||||
}
|
||||
settinsStr, err2 := json.Marshal(secret_sync_settings)
|
||||
if err2 != nil {
|
||||
log.Println("Error encoding set secret sync settings:", err2)
|
||||
return
|
||||
}
|
||||
worker.SetRedis(redisKey, string(settinsStr)) //将当前的密钥信息存入redis
|
||||
}
|
||||
|
||||
// 获取token密钥请求
|
||||
func GetTokenSecretFromUserCenter() (*proto.SecretSyncSettings, error) {
|
||||
url := "https://uc.ljsea.top/tool/sync_system_config"
|
||||
var req proto.SyncSystemConfigRequest
|
||||
proto.SigningKeyRWLock.Lock()
|
||||
defer proto.SigningKeyRWLock.Unlock()
|
||||
req.SecretKeyMd5 = worker.GenerateMD5(string(proto.SigningKey))
|
||||
req.DeviceApp = "StuAcaWorksAI"
|
||||
req.Timestamp = worker.GetCurrentTimestamp()
|
||||
req.Sign = worker.GenerateMD5(req.SecretKeyMd5 + req.DeviceApp + strconv.FormatInt(req.Timestamp, 10))
|
||||
reqBytes, err2 := json.Marshal(req)
|
||||
if err2 != nil {
|
||||
log.Println("GetTokenSecretFromUserCenter json marshal error:", err2)
|
||||
return nil, err2
|
||||
}
|
||||
err, resp := worker.DoPostRequestJSON(url, reqBytes, nil)
|
||||
if err != nil {
|
||||
log.Println("GetTokenSecretFromUserCenter post request error:", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var respObject proto.RequestSyncSecretResp
|
||||
err = json.Unmarshal(resp, &respObject)
|
||||
if err != nil {
|
||||
log.Println("GetTokenSecretFromUserCenter json unmarshal error:", err)
|
||||
return nil, err
|
||||
}
|
||||
//对称加密密钥。通过密钥加 secret_key 取md5
|
||||
secretKeyMd5 := worker.GenerateMD5(proto.TOKEN_SECRET + "_sync_secret")
|
||||
|
||||
//解密返回数据
|
||||
dataContent, err2 := worker.AESDecrypt(respObject.Data, []byte(secretKeyMd5))
|
||||
if err2 != nil {
|
||||
log.Println("GetTokenSecretFromUserCenter aes decrypt error:", err2)
|
||||
return nil, err2
|
||||
}
|
||||
var secretResp proto.SecretSyncSettings
|
||||
err = json.Unmarshal(dataContent, &secretResp)
|
||||
if err != nil {
|
||||
log.Println("GetTokenSecretFromUserCenter json unmarshal error:", err)
|
||||
return nil, err
|
||||
}
|
||||
return &secretResp, nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,13 +1,93 @@
|
|||
package worker
|
||||
|
||||
import "math/rand"
|
||||
import (
|
||||
"crypto/aes"
|
||||
"crypto/cipher"
|
||||
"crypto/md5"
|
||||
"crypto/rand"
|
||||
"encoding/base64"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"io"
|
||||
mrand "math/rand"
|
||||
"time"
|
||||
)
|
||||
|
||||
func GetRandomString(l int) string {
|
||||
str := "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"
|
||||
bytes := []byte(str)
|
||||
var result []byte
|
||||
for i := 0; i < l; i++ {
|
||||
result = append(result, bytes[rand.Intn(len(bytes))])
|
||||
result = append(result, bytes[mrand.Intn(len(bytes))])
|
||||
}
|
||||
return string(result)
|
||||
}
|
||||
|
||||
func GetCurrentTimestamp() int64 {
|
||||
// 获取当前时间戳
|
||||
return time.Now().Unix()
|
||||
}
|
||||
|
||||
// AESEncrypt 函数使用AES-GCM算法对明文进行加密
|
||||
func AESEncrypt(plaintext []byte, key []byte) (string, error) {
|
||||
// 创建AES加密块
|
||||
block, err := aes.NewCipher(key)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
// 创建GCM模式的加密器
|
||||
gcm, err := cipher.NewGCM(block)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
// 生成随机的nonce
|
||||
nonce := make([]byte, gcm.NonceSize())
|
||||
if _, err = io.ReadFull(rand.Reader, nonce); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
// 执行加密操作
|
||||
ciphertext := gcm.Seal(nonce, nonce, plaintext, nil)
|
||||
|
||||
// 将加密结果转换为Base64编码字符串
|
||||
return base64.StdEncoding.EncodeToString(ciphertext), nil
|
||||
}
|
||||
|
||||
// AESDecrypt 函数使用AES-GCM算法对密文进行解密
|
||||
func AESDecrypt(ciphertext string, key []byte) ([]byte, error) {
|
||||
// 将Base64编码的密文转换为字节切片
|
||||
data, err := base64.StdEncoding.DecodeString(ciphertext)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 创建AES加密块
|
||||
block, err := aes.NewCipher(key)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 创建GCM模式的解密器
|
||||
gcm, err := cipher.NewGCM(block)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 提取nonce和真正的密文
|
||||
nonceSize := gcm.NonceSize()
|
||||
if len(data) < nonceSize {
|
||||
return nil, fmt.Errorf("密文长度过短")
|
||||
}
|
||||
nonce, ciphertextBytes := data[:nonceSize], data[nonceSize:]
|
||||
|
||||
// 执行解密操作
|
||||
return gcm.Open(nil, nonce, ciphertextBytes, nil)
|
||||
}
|
||||
|
||||
func GenerateMD5(secretKey string) string {
|
||||
hasher := md5.New()
|
||||
hasher.Write([]byte(secretKey))
|
||||
return hex.EncodeToString(hasher.Sum(nil)) // 将二进制数据转换为十六进制字符串
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue