Files
cmii-uav-watchdog-project/cmii-uav-watchdog/services/auth_service.go
2025-12-06 11:26:05 +08:00

403 lines
11 KiB
Go

package services
import (
models2 "cmii-uav-watchdog-common/models"
"cmii-uav-watchdog-common/totp_tier_one"
"cmii-uav-watchdog-common/totp_tier_two"
"cmii-uav-watchdog-common/utils"
"cmii-uav-watchdog-common/wdd_log"
"cmii-uav-watchdog/config"
"encoding/json"
"errors"
"os"
"sync"
)
// 单例相关变量
var (
authServiceInstance *AuthService
authServiceOnce sync.Once
authServiceMutex sync.Mutex
)
// AuthService 授权服务
type AuthService struct {
mu sync.RWMutex
heartBeatHostInfoMap map[string]models2.HostInfo // 主机信息集合
authorizationInfo *models2.AuthorizationStorage // 授权信息
totpService *TOTPService
initialized bool
}
// NewAuthService 创建授权服务(单例模式)
func NewAuthService() *AuthService {
// 使用sync.Once确保初始化逻辑只执行一次
authServiceOnce.Do(func() {
authServiceMutex.Lock()
defer authServiceMutex.Unlock()
// 如果实例已存在,直接返回
if authServiceInstance != nil {
return
}
// 创建新实例
service := &AuthService{
heartBeatHostInfoMap: make(map[string]models2.HostInfo),
totpService: NewTOTPService(),
initialized: false,
}
// 尝试从本地加载授权信息
service.loadAuthorizationInfo()
// 确保 authorizationInfo 不为 nil
if service.authorizationInfo == nil {
service.authorizationInfo = &models2.AuthorizationStorage{}
}
// 判断 项目级别的 TOTP密钥是否为空
// 若为空 则生成一个 二级TOTP密钥 然后持久化写入到授权文件中
if service.authorizationInfo.SecondTOTPSecret == "" {
secondTOTPSecret, err := totp_tier_two.GenerateTierTwoTOTPSecret()
if err != nil {
wdd_log.Error("生成二级TOTP密钥失败: %v", err)
return
}
service.authorizationInfo.SecondTOTPSecret = secondTOTPSecret
// 持久化写入到授权文件中
err = service.saveAuthorizationInfo()
if err != nil {
wdd_log.Error("持久化写入授权文件失败: %v", err)
return
}
}
// 设置全局实例
authServiceInstance = service
})
return authServiceInstance
}
// AddHostInfo 添加主机信息
func (as *AuthService) AddHostInfo(hostInfo models2.HostInfo) {
as.mu.Lock()
defer as.mu.Unlock()
hostKey, err := totp_tier_one.EncryptHostInfo(hostInfo, as.totpService.tierOneSecret)
if err != nil {
wdd_log.Error("加密主机信息失败: %v", err)
return
}
as.heartBeatHostInfoMap[hostKey] = hostInfo
}
// GenerateAuthorizationFile 生成授权文件
func (as *AuthService) GenerateAuthorizationFile() (*models2.AuthorizationFile, error) {
as.mu.RLock()
defer as.mu.RUnlock()
// 检查是否有主机信息
if len(as.heartBeatHostInfoMap) == 0 {
return nil, errors.New("没有可用的主机信息")
}
// 加密主机信息
encryptedHostMap := make(map[string]models2.HostInfo)
for _, hostInfo := range as.heartBeatHostInfoMap {
encrypted, err := totp_tier_one.EncryptHostInfo(hostInfo, as.totpService.tierOneSecret)
if err != nil {
wdd_log.Error("加密主机信息失败: %v hostInfo NetworkInfo: %v", err, hostInfo.NetInfo)
}
encryptedHostMap[encrypted] = hostInfo
}
// 获取项目命名空间
projectNamespace := config.GetConfig().Project.ProjectNamespace
encryptedNamespace, err := totp_tier_one.Encrypt(projectNamespace, projectNamespace)
if err != nil {
return nil, err
}
// 生成TOTP验证码
tierOneSecret := config.GetConfig().TierOneAuth.TierOneSecret
tierOneTOTPCode, err := totp_tier_one.GenerateTierOneTOTPCode(tierOneSecret)
if err != nil {
wdd_log.Error("生成TOTP验证码失败: %v", err)
return nil, err
}
// 创建授权文件
authFile := &models2.AuthorizationFile{
EncryptedHostMap: encryptedHostMap,
TOTPCode: tierOneTOTPCode,
CurrentTime: utils.CurentTimeString(),
ProjectNamespace: projectNamespace,
EncryptedNamespace: encryptedNamespace,
}
return authFile, nil
}
// ProcessAuthorizationCode 处理授权码
func (as *AuthService) ProcessAuthorizationCode(authCode models2.AuthorizationCode) error {
as.mu.Lock()
defer as.mu.Unlock()
// 验证TOTP
if !as.totpService.VerifyTierOneTOTP(authCode.TOTPCode) {
wdd_log.Error("无效的授权码: TOTP验证失败")
return errors.New("无效的授权码: TOTP验证失败")
}
// 解密项目命名空间
projectNamespace, err := totp_tier_one.Decrypt(authCode.EncryptedNamespace, authCode.ProjectNamespace)
if err != nil {
wdd_log.Error("解密项目命名空间失败: %v", err)
return err
}
// 验证项目命名空间
if projectNamespace != config.GetConfig().Project.ProjectNamespace {
wdd_log.Error("无效的授权码: 项目命名空间不匹配")
return errors.New("无效的授权码: 项目命名空间不匹配")
}
// 解密服务器信息
encryptedServerInfoMap := authCode.EncryptedHostMap
serverInfoMap := make(map[string]models2.HostInfo)
for encrypted, hostInfo := range encryptedServerInfoMap {
isOK, err := totp_tier_one.DecryptHostInfo(encrypted, hostInfo, as.totpService.tierOneSecret)
if !isOK || err != nil {
wdd_log.Error("解密服务器信息失败: %v hostInfo NetworkInfo: %v", err, hostInfo.NetInfo)
continue
}
// 将解密后的主机信息添加到serverInfoMap中
serverInfoMap[encrypted] = hostInfo
// 将解密后的主机信息添加到hostInfoSet中
as.heartBeatHostInfoMap[encrypted] = hostInfo
}
// 获取当前时间
now := utils.CurentTimeString()
// 准备初次授权时间
firstAuthTime := now
if as.initialized {
if as.authorizationInfo.FirstAuthTime != "" {
wdd_log.Debug("已存在初次授权时间: %s", as.authorizationInfo.FirstAuthTime)
firstAuthTime = as.authorizationInfo.FirstAuthTime
}
}
// 计算授权时间偏移
nowTime, err := utils.ParseTimeString(now)
if err != nil {
wdd_log.Error("解析当前时间失败: %v", err)
return err
}
firstAuthTimeTime, err := utils.ParseTimeString(firstAuthTime)
if err != nil {
wdd_log.Error("解析初次授权时间失败: %v", err)
return err
}
timeOffset := nowTime.Unix() - firstAuthTimeTime.Unix()
// 加密授权码
authCodeJSON, err := json.Marshal(authCode)
if err != nil {
wdd_log.Error("加密授权码失败: %v", err)
return err
}
// 加密授权码 全部加密
encryptedCode, err := totp_tier_one.Encrypt(string(authCodeJSON), as.totpService.tierOneSecret)
if err != nil {
wdd_log.Error("加密授权码失败: %v", err)
return err
}
// 保存授权信息
as.authorizationInfo = &models2.AuthorizationStorage{
EncryptedAuthrizationCode: encryptedCode,
FirstAuthTime: firstAuthTime,
TimeOffset: timeOffset,
AuthorizedHostMap: serverInfoMap,
SecondTOTPSecret: as.authorizationInfo.SecondTOTPSecret,
ProjectNamespace: projectNamespace,
EncryptedNamespace: authCode.EncryptedNamespace,
}
// 保存到文件
if err := as.saveAuthorizationInfo(); err != nil {
wdd_log.Error("保存授权信息失败: %v", err)
return err
}
// 设置初始化状态
as.initialized = true
return nil
}
// IsHostAuthorized 检查主机是否已授权
func (as *AuthService) IsHostAuthorized(hostInfo models2.HostInfo) bool {
as.mu.RLock()
defer as.mu.RUnlock()
if !as.initialized {
return false
}
// 加密主机信息
encrypted, err := totp_tier_one.EncryptHostInfo(hostInfo, as.totpService.tierOneSecret)
if err != nil {
return false
}
// 检查是否在已授权列表中
_, ok := as.authorizationInfo.AuthorizedHostMap[encrypted]
return ok
}
// VerifyAuthorizationTime 验证授权时间有效性
func (as *AuthService) VerifyAuthorizationTime() {
as.mu.RLock()
defer as.mu.RUnlock()
if !as.initialized {
return
}
// 获取当前时间和存储的初次授权时间
now := utils.CurentTimeString()
storedOffset := as.authorizationInfo.TimeOffset
nowTime, err := utils.ParseTimeString(now)
if err != nil {
return
}
firstAuthTime, err := utils.ParseTimeString(as.authorizationInfo.FirstAuthTime)
if err != nil {
return
}
// 计算实际时间偏移
actualOffset := nowTime.Unix() - firstAuthTime.Unix()
// 计算偏差
offsetDiff := actualOffset - storedOffset
// 获取允许的时间偏移
allowedOffset := config.GetConfig().TierOneAuth.TimeOffsetAllowed
// 检查偏差是否超过允许范围
if offsetDiff > allowedOffset || offsetDiff < -allowedOffset {
wdd_log.Warn("检测到时间篡改! 存储偏移: %d, 实际偏移: %d, 差值: %d",
storedOffset, actualOffset, offsetDiff)
// 这里可以添加更多的处理逻辑,例如发送警告、禁用授权等
}
}
// GetAuthorizationInfo 获取授权信息
func (as *AuthService) GetAuthorizationInfo() (models2.AuthorizationStorage, error) {
as.mu.RLock()
defer as.mu.RUnlock()
if !as.initialized {
return models2.AuthorizationStorage{}, errors.New("未授权")
}
// 解密授权码
decryptedCode, err := totp_tier_one.Decrypt(as.authorizationInfo.EncryptedAuthrizationCode, as.totpService.tierOneSecret)
if err != nil {
return models2.AuthorizationStorage{}, err
}
// 解析授权码
var authorizationInfo models2.AuthorizationStorage
if err := json.Unmarshal([]byte(decryptedCode), &authorizationInfo); err != nil {
return models2.AuthorizationStorage{}, err
}
return authorizationInfo, nil
}
// GetAllHostInfo 获取所有主机信息(心跳检测到的所有主机)
func (as *AuthService) GetAllHostInfo() map[string]models2.HostInfo {
as.mu.RLock()
defer as.mu.RUnlock()
// 返回主机信息集合的副本
result := make(map[string]models2.HostInfo, len(as.heartBeatHostInfoMap))
for k, v := range as.heartBeatHostInfoMap {
result[k] = v
}
return result
}
// GetAllAuthorizedHosts 获取所有已授权的主机信息
func (as *AuthService) GetAllAuthorizedHosts() map[string]models2.HostInfo {
as.mu.RLock()
defer as.mu.RUnlock()
if !as.initialized || as.authorizationInfo == nil {
return make(map[string]models2.HostInfo)
}
// 返回已授权主机信息的副本
result := make(map[string]models2.HostInfo, len(as.authorizationInfo.AuthorizedHostMap))
for k, v := range as.authorizationInfo.AuthorizedHostMap {
result[k] = v
}
return result
}
// saveAuthorizationInfo 保存授权信息到文件
func (as *AuthService) saveAuthorizationInfo() error {
data, err := json.Marshal(as.authorizationInfo)
if err != nil {
return err
}
return os.WriteFile(config.GetConfig().TierOneAuth.LocalAuthFilePath, data, 0600)
}
// loadAuthorizationInfo 从文件加载授权信息
func (as *AuthService) loadAuthorizationInfo() {
filePath := config.GetConfig().TierOneAuth.LocalAuthFilePath
// 检查文件是否存在
if _, err := os.Stat(filePath); os.IsNotExist(err) {
// 初始化一个空的授权信息
as.authorizationInfo = &models2.AuthorizationStorage{}
return
}
data, err := os.ReadFile(filePath)
if err != nil {
wdd_log.Error("读取授权文件失败: %v", err)
// 初始化一个空的授权信息
as.authorizationInfo = &models2.AuthorizationStorage{}
return
}
var authInfo *models2.AuthorizationStorage
if err := json.Unmarshal(data, &authInfo); err != nil {
wdd_log.Error("解析授权文件失败: %v", err)
// 初始化一个空的授权信息
as.authorizationInfo = &models2.AuthorizationStorage{}
return
}
if authInfo == nil {
authInfo = &models2.AuthorizationStorage{}
}
as.authorizationInfo = authInfo
as.initialized = true
}