添加配置加载和授权服务初始化,更新授权模型引用,重构授权文件生成和处理逻辑,优化 TOTP 密钥生成及错误处理

This commit is contained in:
zeaslity
2025-03-13 10:36:23 +08:00
parent 25dbc87a73
commit 34147b2f69
5 changed files with 133 additions and 55 deletions

View File

@@ -0,0 +1,28 @@
package models
import "time"
// AuthorizationFile 授权文件模型
type AuthorizationFile struct {
EncryptedHosts []string `json:"encrypted_hosts"` // 加密后的主机信息列表
TOTPCode string `json:"totp_code"` // TOTP验证码
CurrentTime time.Time `json:"current_time"` // 当前系统时间
FirstAuthTime time.Time `json:"first_auth_time"` // 初次授权时间
TimeOffset int64 `json:"time_offset"` // 授权时间偏移
}
// AuthorizationCode 授权码模型
type AuthorizationCode struct {
TOTPCode string `json:"totp_code"` // TOTP验证码
CurrentTime time.Time `json:"current_time"` // 当前系统时间
EncryptedHosts []string `json:"encrypted_hosts"` // 授权主机的加密字符串列表
}
// AuthorizationStorage 授权存储信息
type AuthorizationStorage struct {
EncryptedCode string `json:"encrypted_code"` // 加密后的授权码
FirstAuthTime time.Time `json:"first_auth_time"` // 初次授权时间
TimeOffset int64 `json:"time_offset"` // 授权时间偏移
AuthorizedHosts []string `json:"authorized_hosts"` // 已授权主机列表
SecondTOTPSecret string `json:"second_totp_secret"` // 第二级的totp密钥
}

View File

@@ -1,10 +1,11 @@
package controllers package controllers
import ( import (
models2 "cmii-uav-watchdog-common/models"
"cmii-uav-watchdog/models" "cmii-uav-watchdog/models"
"cmii-uav-watchdog/services" "cmii-uav-watchdog/services"
"net/http" "net/http"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
) )
@@ -32,7 +33,7 @@ func (ac *AuthController) GenerateAuthFile(c *gin.Context) {
}) })
return return
} }
c.JSON(http.StatusOK, models.Response{ c.JSON(http.StatusOK, models.Response{
Code: 200, Code: 200,
Message: "生成授权文件成功", Message: "生成授权文件成功",
@@ -42,7 +43,7 @@ func (ac *AuthController) GenerateAuthFile(c *gin.Context) {
// ReceiveAuthCode 接收授权码 // ReceiveAuthCode 接收授权码
func (ac *AuthController) ReceiveAuthCode(c *gin.Context) { func (ac *AuthController) ReceiveAuthCode(c *gin.Context) {
var authCode models.AuthorizationCode var authCode models2.AuthorizationCode
if err := c.ShouldBindJSON(&authCode); err != nil { if err := c.ShouldBindJSON(&authCode); err != nil {
c.JSON(http.StatusBadRequest, models.Response{ c.JSON(http.StatusBadRequest, models.Response{
Code: 400, Code: 400,
@@ -51,7 +52,7 @@ func (ac *AuthController) ReceiveAuthCode(c *gin.Context) {
}) })
return return
} }
// 处理授权码 // 处理授权码
err := ac.authService.ProcessAuthorizationCode(authCode) err := ac.authService.ProcessAuthorizationCode(authCode)
if err != nil { if err != nil {
@@ -62,7 +63,7 @@ func (ac *AuthController) ReceiveAuthCode(c *gin.Context) {
}) })
return return
} }
c.JSON(http.StatusOK, models.Response{ c.JSON(http.StatusOK, models.Response{
Code: 200, Code: 200,
Message: "处理授权码成功", Message: "处理授权码成功",
@@ -74,7 +75,7 @@ func (ac *AuthController) ReceiveAuthCode(c *gin.Context) {
func (ac *AuthController) NotifyAuthInfo(c *gin.Context) { func (ac *AuthController) NotifyAuthInfo(c *gin.Context) {
// 获取授权信息 // 获取授权信息
authInfo := ac.authService.GetAuthorizationInfo() authInfo := ac.authService.GetAuthorizationInfo()
c.JSON(http.StatusOK, models.Response{ c.JSON(http.StatusOK, models.Response{
Code: 200, Code: 200,
Message: "获取授权信息成功", Message: "获取授权信息成功",

View File

@@ -10,8 +10,19 @@ import (
func main() { func main() {
// 初始化配置信息
err := config.LoadConfig("./config/config.yaml")
if err != nil {
log.Fatalf("加载配置文件失败: %v", err)
return
}
// 初始化授权服务 // 初始化授权服务
authService := services.NewAuthService() authService := services.NewAuthService()
if authService == nil {
log.Fatalf("初始化授权服务失败")
return
}
// 启动授权码检测定时任务 // 启动授权码检测定时任务
go func() { go func() {

View File

@@ -3,11 +3,9 @@ package services
import ( import (
models2 "cmii-uav-watchdog-common/models" models2 "cmii-uav-watchdog-common/models"
"cmii-uav-watchdog/config" "cmii-uav-watchdog/config"
"cmii-uav-watchdog/models"
"cmii-uav-watchdog/utils" "cmii-uav-watchdog/utils"
"encoding/json" "encoding/json"
"errors" "errors"
"io/ioutil"
"log" "log"
"os" "os"
"sync" "sync"
@@ -17,8 +15,8 @@ import (
// AuthService 授权服务 // AuthService 授权服务
type AuthService struct { type AuthService struct {
mu sync.RWMutex mu sync.RWMutex
hostInfoSet map[string]models2.HostInfo // 主机信息集合 hostInfoSet map[string]models2.HostInfo // 主机信息集合
authorizationInfo models.AuthorizationStorage // 授权信息 authorizationInfo models2.AuthorizationStorage // 授权信息
totpService *TOTPService totpService *TOTPService
initialized bool initialized bool
} }
@@ -30,10 +28,28 @@ func NewAuthService() *AuthService {
totpService: NewTOTPService(), totpService: NewTOTPService(),
initialized: false, initialized: false,
} }
// 尝试从本地加载授权信息 // 尝试从本地加载授权信息
service.loadAuthorizationInfo() service.loadAuthorizationInfo()
// 判断 项目级别的 TOTP密钥是否为空
// 若为空 则生成一个 二级TOTP密钥 然后持久化写入到授权文件中
if service.authorizationInfo.SecondTOTPSecret == "" {
secondTOTPSecret, err := service.totpService.GenerateTOTPSecret()
if err != nil {
log.Printf("生成二级TOTP密钥失败: %v", err)
return nil
}
service.authorizationInfo.SecondTOTPSecret = secondTOTPSecret
// 持久化写入到授权文件中
err = service.saveAuthorizationInfo()
if err != nil {
log.Printf("持久化写入授权文件失败: %v", err)
return nil
}
}
return service return service
} }
@@ -41,39 +57,39 @@ func NewAuthService() *AuthService {
func (as *AuthService) AddHostInfo(hostInfo models2.HostInfo) { func (as *AuthService) AddHostInfo(hostInfo models2.HostInfo) {
as.mu.Lock() as.mu.Lock()
defer as.mu.Unlock() defer as.mu.Unlock()
hostKey := utils.GenerateHostKey(hostInfo) hostKey := utils.GenerateHostKey(hostInfo)
as.hostInfoSet[hostKey] = hostInfo as.hostInfoSet[hostKey] = hostInfo
} }
// GenerateAuthorizationFile 生成授权文件 // GenerateAuthorizationFile 生成授权文件
func (as *AuthService) GenerateAuthorizationFile() (*models.AuthorizationFile, error) { func (as *AuthService) GenerateAuthorizationFile() (*models2.AuthorizationFile, error) {
as.mu.RLock() as.mu.RLock()
defer as.mu.RUnlock() defer as.mu.RUnlock()
// 检查是否有主机信息 // 检查是否有主机信息
if len(as.hostInfoSet) == 0 { if len(as.hostInfoSet) == 0 {
return nil, errors.New("没有可用的主机信息") return nil, errors.New("没有可用的主机信息")
} }
// 生成TOTP验证码 // 生成TOTP验证码
totpCode, err := as.totpService.GenerateTOTP() totpCode, err := as.totpService.GenerateTOTP()
if err != nil { if err != nil {
return nil, err return nil, err
} }
// 获取当前时间 // 获取当前时间
now := time.Now() now := time.Now()
// 准备初次授权时间 // 准备初次授权时间
firstAuthTime := now firstAuthTime := now
if as.initialized { if as.initialized {
firstAuthTime = as.authorizationInfo.FirstAuthTime firstAuthTime = as.authorizationInfo.FirstAuthTime
} }
// 计算时间偏移 // 计算时间偏移
timeOffset := now.Unix() - firstAuthTime.Unix() timeOffset := now.Unix() - firstAuthTime.Unix()
// 加密主机信息 // 加密主机信息
encryptedHosts := make([]string, 0) encryptedHosts := make([]string, 0)
for _, hostInfo := range as.hostInfoSet { for _, hostInfo := range as.hostInfoSet {
@@ -83,67 +99,67 @@ func (as *AuthService) GenerateAuthorizationFile() (*models.AuthorizationFile, e
} }
encryptedHosts = append(encryptedHosts, encrypted) encryptedHosts = append(encryptedHosts, encrypted)
} }
// 创建授权文件 // 创建授权文件
authFile := &models.AuthorizationFile{ authFile := &models2.AuthorizationFile{
EncryptedHosts: encryptedHosts, EncryptedHosts: encryptedHosts,
TOTPCode: totpCode, TOTPCode: totpCode,
CurrentTime: now, CurrentTime: now,
FirstAuthTime: firstAuthTime, FirstAuthTime: firstAuthTime,
TimeOffset: timeOffset, TimeOffset: timeOffset,
} }
return authFile, nil return authFile, nil
} }
// ProcessAuthorizationCode 处理授权码 // ProcessAuthorizationCode 处理授权码
func (as *AuthService) ProcessAuthorizationCode(code models.AuthorizationCode) error { func (as *AuthService) ProcessAuthorizationCode(code models2.AuthorizationCode) error {
as.mu.Lock() as.mu.Lock()
defer as.mu.Unlock() defer as.mu.Unlock()
// 验证TOTP // 验证TOTP
if err := as.totpService.VerifyTOTP(code.TOTPCode); err != nil { if err := as.totpService.VerifyTOTP(code.TOTPCode); err != nil {
return errors.New("无效的授权码: TOTP验证失败") return errors.New("无效的授权码: TOTP验证失败")
} }
// 获取当前时间 // 获取当前时间
now := time.Now() now := time.Now()
// 准备初次授权时间 // 准备初次授权时间
firstAuthTime := now firstAuthTime := now
if as.initialized { if as.initialized {
firstAuthTime = as.authorizationInfo.FirstAuthTime firstAuthTime = as.authorizationInfo.FirstAuthTime
} }
// 计算授权时间偏移 // 计算授权时间偏移
timeOffset := now.Unix() - code.CurrentTime.Unix() timeOffset := now.Unix() - code.CurrentTime.Unix()
// 加密授权码 // 加密授权码
authCodeJSON, err := json.Marshal(code) authCodeJSON, err := json.Marshal(code)
if err != nil { if err != nil {
return err return err
} }
encryptedCode, err := utils.Encrypt(string(authCodeJSON), config.GetConfig().Auth.Secret) encryptedCode, err := utils.Encrypt(string(authCodeJSON), config.GetConfig().Auth.Secret)
if err != nil { if err != nil {
return err return err
} }
// 保存授权信息 // 保存授权信息
as.authorizationInfo = models.AuthorizationStorage{ as.authorizationInfo = models2.AuthorizationStorage{
EncryptedCode: encryptedCode, EncryptedCode: encryptedCode,
FirstAuthTime: firstAuthTime, FirstAuthTime: firstAuthTime,
TimeOffset: timeOffset, TimeOffset: timeOffset,
AuthorizedHosts: code.EncryptedHosts, AuthorizedHosts: code.EncryptedHosts,
} }
// 保存到文件 // 保存到文件
if err := as.saveAuthorizationInfo(); err != nil { if err := as.saveAuthorizationInfo(); err != nil {
return err return err
} }
as.initialized = true as.initialized = true
return nil return nil
} }
@@ -151,24 +167,24 @@ func (as *AuthService) ProcessAuthorizationCode(code models.AuthorizationCode) e
func (as *AuthService) IsHostAuthorized(hostInfo models2.HostInfo) bool { func (as *AuthService) IsHostAuthorized(hostInfo models2.HostInfo) bool {
as.mu.RLock() as.mu.RLock()
defer as.mu.RUnlock() defer as.mu.RUnlock()
if !as.initialized { if !as.initialized {
return false return false
} }
// 加密主机信息 // 加密主机信息
encrypted, err := utils.EncryptHostInfo(hostInfo) encrypted, err := utils.EncryptHostInfo(hostInfo)
if err != nil { if err != nil {
return false return false
} }
// 检查是否在已授权列表中 // 检查是否在已授权列表中
for _, host := range as.authorizationInfo.AuthorizedHosts { for _, host := range as.authorizationInfo.AuthorizedHosts {
if host == encrypted { if host == encrypted {
return true return true
} }
} }
return false return false
} }
@@ -176,24 +192,24 @@ func (as *AuthService) IsHostAuthorized(hostInfo models2.HostInfo) bool {
func (as *AuthService) VerifyAuthorizationTime() { func (as *AuthService) VerifyAuthorizationTime() {
as.mu.RLock() as.mu.RLock()
defer as.mu.RUnlock() defer as.mu.RUnlock()
if !as.initialized { if !as.initialized {
return return
} }
// 获取当前时间和存储的初次授权时间 // 获取当前时间和存储的初次授权时间
now := time.Now() now := time.Now()
storedOffset := as.authorizationInfo.TimeOffset storedOffset := as.authorizationInfo.TimeOffset
// 计算实际时间偏移 // 计算实际时间偏移
actualOffset := now.Unix() - as.authorizationInfo.FirstAuthTime.Unix() actualOffset := now.Unix() - as.authorizationInfo.FirstAuthTime.Unix()
// 计算偏差 // 计算偏差
offsetDiff := actualOffset - storedOffset offsetDiff := actualOffset - storedOffset
// 获取允许的时间偏移 // 获取允许的时间偏移
allowedOffset := config.GetConfig().Auth.TimeOffsetAllowed allowedOffset := config.GetConfig().Auth.TimeOffsetAllowed
// 检查偏差是否超过允许范围 // 检查偏差是否超过允许范围
if offsetDiff > allowedOffset || offsetDiff < -allowedOffset { if offsetDiff > allowedOffset || offsetDiff < -allowedOffset {
log.Printf("检测到时间篡改! 存储偏移: %d, 实际偏移: %d, 差值: %d", log.Printf("检测到时间篡改! 存储偏移: %d, 实际偏移: %d, 差值: %d",
@@ -206,14 +222,14 @@ func (as *AuthService) VerifyAuthorizationTime() {
func (as *AuthService) GetAuthorizationInfo() interface{} { func (as *AuthService) GetAuthorizationInfo() interface{} {
as.mu.RLock() as.mu.RLock()
defer as.mu.RUnlock() defer as.mu.RUnlock()
if !as.initialized { if !as.initialized {
return map[string]interface{}{ return map[string]interface{}{
"authorized": false, "authorized": false,
"message": "未授权", "message": "未授权",
} }
} }
return map[string]interface{}{ return map[string]interface{}{
"authorized": true, "authorized": true,
"authorized_hosts": len(as.authorizationInfo.AuthorizedHosts), "authorized_hosts": len(as.authorizationInfo.AuthorizedHosts),
@@ -227,31 +243,31 @@ func (as *AuthService) saveAuthorizationInfo() error {
if err != nil { if err != nil {
return err return err
} }
return ioutil.WriteFile(config.GetConfig().Auth.AuthFilePath, data, 0600) return os.WriteFile(config.GetConfig().Auth.AuthFilePath, data, 0600)
} }
// loadAuthorizationInfo 从文件加载授权信息 // loadAuthorizationInfo 从文件加载授权信息
func (as *AuthService) loadAuthorizationInfo() { func (as *AuthService) loadAuthorizationInfo() {
filePath := config.GetConfig().Auth.AuthFilePath filePath := config.GetConfig().Auth.AuthFilePath
// 检查文件是否存在 // 检查文件是否存在
if _, err := os.Stat(filePath); os.IsNotExist(err) { if _, err := os.Stat(filePath); os.IsNotExist(err) {
return return
} }
data, err := ioutil.ReadFile(filePath) data, err := os.ReadFile(filePath)
if err != nil { if err != nil {
log.Printf("读取授权文件失败: %v", err) log.Printf("读取授权文件失败: %v", err)
return return
} }
var authInfo models.AuthorizationStorage var authInfo models2.AuthorizationStorage
if err := json.Unmarshal(data, &authInfo); err != nil { if err := json.Unmarshal(data, &authInfo); err != nil {
log.Printf("解析授权文件失败: %v", err) log.Printf("解析授权文件失败: %v", err)
return return
} }
as.authorizationInfo = authInfo as.authorizationInfo = authInfo
as.initialized = true as.initialized = true
} }

View File

@@ -3,8 +3,10 @@ package services
import ( import (
"cmii-uav-watchdog/config" "cmii-uav-watchdog/config"
"errors" "errors"
"log"
"time" "time"
otp "cmii-uav-watchdog-otp"
"cmii-uav-watchdog-otp/totp" "cmii-uav-watchdog-otp/totp"
) )
@@ -41,3 +43,23 @@ func (ts *TOTPService) VerifyTOTP(code string) error {
return nil return nil
} }
// GenerateTOTPSecret 生成TOTP密钥
func (ts *TOTPService) GenerateTOTPSecret() (string, error) {
secret, err := totp.Generate(totp.GenerateOpts{
SecretSize: 32,
Issuer: "cmii-uav-watchdog",
AccountName: "cmii-uav-watchdog",
Period: 30,
Secret: []byte{},
Digits: otp.DigitsSix,
Algorithm: otp.AlgorithmSHA1,
Rand: nil,
})
if err != nil {
log.Printf("生成TOTP密钥失败: %v", err)
return "", err
}
return secret.Secret(), nil
}