296 lines
6.9 KiB
Go
296 lines
6.9 KiB
Go
package services
|
|
|
|
import (
|
|
models2 "cmii-uav-watchdog-common/models"
|
|
"cmii-uav-watchdog/config"
|
|
"cmii-uav-watchdog/utils"
|
|
"encoding/json"
|
|
"errors"
|
|
"log"
|
|
"os"
|
|
"sync"
|
|
"time"
|
|
)
|
|
|
|
// 单例相关变量
|
|
var (
|
|
authServiceInstance *AuthService
|
|
authServiceOnce sync.Once
|
|
authServiceMutex sync.Mutex
|
|
)
|
|
|
|
// AuthService 授权服务
|
|
type AuthService struct {
|
|
mu sync.RWMutex
|
|
hostInfoSet 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{
|
|
hostInfoSet: make(map[string]models2.HostInfo),
|
|
totpService: NewTOTPService(),
|
|
initialized: false,
|
|
}
|
|
|
|
// 尝试从本地加载授权信息
|
|
service.loadAuthorizationInfo()
|
|
|
|
// 判断 项目级别的 TOTP密钥是否为空
|
|
// 若为空 则生成一个 二级TOTP密钥 然后持久化写入到授权文件中
|
|
if service.authorizationInfo.SecondTOTPSecret == "" {
|
|
secondTOTPSecret, err := service.totpService.GenerateTierTwoTOTPSecret()
|
|
if err != nil {
|
|
log.Printf("生成二级TOTP密钥失败: %v", err)
|
|
return
|
|
}
|
|
service.authorizationInfo.SecondTOTPSecret = secondTOTPSecret
|
|
|
|
// 持久化写入到授权文件中
|
|
err = service.saveAuthorizationInfo()
|
|
if err != nil {
|
|
log.Printf("持久化写入授权文件失败: %v", err)
|
|
return
|
|
}
|
|
}
|
|
|
|
// 设置全局实例
|
|
authServiceInstance = service
|
|
})
|
|
|
|
return authServiceInstance
|
|
}
|
|
|
|
// AddHostInfo 添加主机信息
|
|
func (as *AuthService) AddHostInfo(hostInfo models2.HostInfo) {
|
|
as.mu.Lock()
|
|
defer as.mu.Unlock()
|
|
|
|
hostKey := utils.GenerateHostKey(hostInfo)
|
|
as.hostInfoSet[hostKey] = hostInfo
|
|
}
|
|
|
|
// GenerateAuthorizationFile 生成授权文件
|
|
func (as *AuthService) GenerateAuthorizationFile() (*models2.AuthorizationFile, error) {
|
|
as.mu.RLock()
|
|
defer as.mu.RUnlock()
|
|
|
|
// 检查是否有主机信息
|
|
if len(as.hostInfoSet) == 0 {
|
|
return nil, errors.New("没有可用的主机信息")
|
|
}
|
|
|
|
// 生成TOTP验证码
|
|
totpCode, err := as.totpService.GenerateTierTwoTOTPCode(as.authorizationInfo.SecondTOTPSecret)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// 获取当前时间
|
|
now := time.Now()
|
|
|
|
// 准备初次授权时间
|
|
firstAuthTime := now
|
|
if as.initialized {
|
|
firstAuthTime = as.authorizationInfo.FirstAuthTime
|
|
}
|
|
|
|
// 计算时间偏移
|
|
timeOffset := now.Unix() - firstAuthTime.Unix()
|
|
|
|
// 加密主机信息
|
|
encryptedHosts := make([]string, 0)
|
|
for _, hostInfo := range as.hostInfoSet {
|
|
encrypted, err := utils.EncryptHostInfo(hostInfo)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
encryptedHosts = append(encryptedHosts, encrypted)
|
|
}
|
|
|
|
// 创建授权文件
|
|
authFile := &models2.AuthorizationFile{
|
|
EncryptedHosts: encryptedHosts,
|
|
TOTPCode: totpCode,
|
|
CurrentTime: now,
|
|
FirstAuthTime: firstAuthTime,
|
|
TimeOffset: timeOffset,
|
|
}
|
|
|
|
return authFile, nil
|
|
}
|
|
|
|
// ProcessAuthorizationCode 处理授权码
|
|
func (as *AuthService) ProcessAuthorizationCode(code models2.AuthorizationCode) error {
|
|
as.mu.Lock()
|
|
defer as.mu.Unlock()
|
|
|
|
// 验证TOTP
|
|
if !as.totpService.VerifyTierTwoTOTPCode(code.TOTPCode, as.authorizationInfo.SecondTOTPSecret) {
|
|
return errors.New("无效的授权码: TOTP验证失败")
|
|
}
|
|
|
|
// 获取当前时间
|
|
now := time.Now()
|
|
|
|
// 准备初次授权时间
|
|
firstAuthTime := now
|
|
if as.initialized {
|
|
firstAuthTime = as.authorizationInfo.FirstAuthTime
|
|
}
|
|
|
|
// 计算授权时间偏移
|
|
timeOffset := now.Unix() - code.CurrentTime.Unix()
|
|
|
|
// 加密授权码
|
|
authCodeJSON, err := json.Marshal(code)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
encryptedCode, err := utils.Encrypt(string(authCodeJSON), config.GetConfig().Auth.Secret)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// 保存授权信息
|
|
as.authorizationInfo = models2.AuthorizationStorage{
|
|
EncryptedCode: encryptedCode,
|
|
FirstAuthTime: firstAuthTime,
|
|
TimeOffset: timeOffset,
|
|
AuthorizedHosts: code.EncryptedHosts,
|
|
}
|
|
|
|
// 保存到文件
|
|
if err := as.saveAuthorizationInfo(); err != nil {
|
|
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 := utils.EncryptHostInfo(hostInfo)
|
|
if err != nil {
|
|
return false
|
|
}
|
|
|
|
// 检查是否在已授权列表中
|
|
for _, host := range as.authorizationInfo.AuthorizedHosts {
|
|
if host == encrypted {
|
|
return true
|
|
}
|
|
}
|
|
|
|
return false
|
|
}
|
|
|
|
// VerifyAuthorizationTime 验证授权时间有效性
|
|
func (as *AuthService) VerifyAuthorizationTime() {
|
|
as.mu.RLock()
|
|
defer as.mu.RUnlock()
|
|
|
|
if !as.initialized {
|
|
return
|
|
}
|
|
|
|
// 获取当前时间和存储的初次授权时间
|
|
now := time.Now()
|
|
storedOffset := as.authorizationInfo.TimeOffset
|
|
|
|
// 计算实际时间偏移
|
|
actualOffset := now.Unix() - as.authorizationInfo.FirstAuthTime.Unix()
|
|
|
|
// 计算偏差
|
|
offsetDiff := actualOffset - storedOffset
|
|
|
|
// 获取允许的时间偏移
|
|
allowedOffset := config.GetConfig().Auth.TimeOffsetAllowed
|
|
|
|
// 检查偏差是否超过允许范围
|
|
if offsetDiff > allowedOffset || offsetDiff < -allowedOffset {
|
|
log.Printf("检测到时间篡改! 存储偏移: %d, 实际偏移: %d, 差值: %d",
|
|
storedOffset, actualOffset, offsetDiff)
|
|
// 这里可以添加更多的处理逻辑,例如发送警告、禁用授权等
|
|
}
|
|
}
|
|
|
|
// GetAuthorizationInfo 获取授权信息
|
|
func (as *AuthService) GetAuthorizationInfo() interface{} {
|
|
as.mu.RLock()
|
|
defer as.mu.RUnlock()
|
|
|
|
if !as.initialized {
|
|
return map[string]interface{}{
|
|
"authorized": false,
|
|
"message": "未授权",
|
|
}
|
|
}
|
|
|
|
return map[string]interface{}{
|
|
"authorized": true,
|
|
"authorized_hosts": len(as.authorizationInfo.AuthorizedHosts),
|
|
"first_auth_time": as.authorizationInfo.FirstAuthTime,
|
|
}
|
|
}
|
|
|
|
// saveAuthorizationInfo 保存授权信息到文件
|
|
func (as *AuthService) saveAuthorizationInfo() error {
|
|
data, err := json.Marshal(as.authorizationInfo)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
return os.WriteFile(config.GetConfig().Auth.AuthFilePath, data, 0600)
|
|
}
|
|
|
|
// loadAuthorizationInfo 从文件加载授权信息
|
|
func (as *AuthService) loadAuthorizationInfo() {
|
|
filePath := config.GetConfig().Auth.AuthFilePath
|
|
|
|
// 检查文件是否存在
|
|
if _, err := os.Stat(filePath); os.IsNotExist(err) {
|
|
return
|
|
}
|
|
|
|
data, err := os.ReadFile(filePath)
|
|
if err != nil {
|
|
log.Printf("读取授权文件失败: %v", err)
|
|
return
|
|
}
|
|
|
|
var authInfo models2.AuthorizationStorage
|
|
if err := json.Unmarshal(data, &authInfo); err != nil {
|
|
log.Printf("解析授权文件失败: %v", err)
|
|
return
|
|
}
|
|
|
|
as.authorizationInfo = authInfo
|
|
as.initialized = true
|
|
}
|