extends Node class_name SecurityManager ## 安全管理器 ## 提供输入验证、防护措施和安全检查功能 # 安全配置常量(从SecurityConfig获取) const DEFAULT_MAX_MESSAGE_LENGTH = 500 const DEFAULT_MAX_USERNAME_LENGTH = 50 const DEFAULT_MAX_CHARACTER_NAME_LENGTH = 20 const DEFAULT_MIN_CHARACTER_NAME_LENGTH = 2 # 恶意模式检测 const MALICIOUS_PATTERNS = [ " Dictionary: """ 验证用户输入的安全性和有效性 @param input: 输入字符串 @param input_type: 输入类型 ("username", "character_name", "message") @return: 验证结果 {valid: bool, error: String, sanitized: String} """ var result = { "valid": false, "error": "", "sanitized": "" } # 基础检查 if input == null: result.error = "输入不能为空" return result # 转换为字符串并去除首尾空格 var clean_input = str(input).strip_edges() # 获取配置值 var max_username_length = SecurityConfig.get_config("input_validation", "max_username_length", DEFAULT_MAX_USERNAME_LENGTH) var max_character_name_length = SecurityConfig.get_config("input_validation", "max_character_name_length", DEFAULT_MAX_CHARACTER_NAME_LENGTH) var min_character_name_length = SecurityConfig.get_config("input_validation", "min_character_name_length", DEFAULT_MIN_CHARACTER_NAME_LENGTH) var max_message_length = SecurityConfig.get_config("input_validation", "max_message_length", DEFAULT_MAX_MESSAGE_LENGTH) # 长度检查 match input_type: "username": if clean_input.length() == 0: result.error = "用户名不能为空" return result if clean_input.length() > max_username_length: result.error = "用户名长度不能超过 %d 个字符" % max_username_length return result "character_name": if clean_input.length() < min_character_name_length: result.error = "角色名称至少需要 %d 个字符" % min_character_name_length return result if clean_input.length() > max_character_name_length: result.error = "角色名称不能超过 %d 个字符" % max_character_name_length return result "message": if clean_input.length() == 0: result.error = "消息不能为空" return result if clean_input.length() > max_message_length: result.error = "消息长度不能超过 %d 个字符" % max_message_length return result # 恶意内容检测 var malicious_check = detect_malicious_content(clean_input) if not malicious_check.safe: result.error = "输入包含不安全内容: " + malicious_check.reason return result # 清理输入 var sanitized = sanitize_input(clean_input) result.valid = true result.sanitized = sanitized return result ## 检测恶意内容 static func detect_malicious_content(input: String) -> Dictionary: """ 检测输入中的恶意内容 @param input: 输入字符串 @return: {safe: bool, reason: String} """ var input_lower = input.to_lower() # 检查脚本注入 for pattern in MALICIOUS_PATTERNS: if input_lower.contains(pattern.to_lower()): return { "safe": false, "reason": "检测到潜在的脚本注入: " + pattern } # 检查SQL注入(虽然我们不使用SQL,但作为额外防护) for pattern in SQL_INJECTION_PATTERNS: if input_lower.contains(pattern.to_lower()): return { "safe": false, "reason": "检测到潜在的注入攻击: " + pattern } # 检查过长的重复字符(可能的DoS攻击) if has_excessive_repetition(input): return { "safe": false, "reason": "检测到异常的重复字符模式" } return {"safe": true, "reason": ""} ## 检查过度重复字符 static func has_excessive_repetition(input: String) -> bool: """ 检查字符串是否包含过度重复的字符 @param input: 输入字符串 @return: 是否包含过度重复 """ if input.length() < 10: return false var char_counts = {} for character in input: char_counts[character] = char_counts.get(character, 0) + 1 # 如果任何字符重复超过输入长度的70%,认为是异常 var threshold = input.length() * 0.7 for count in char_counts.values(): if count > threshold: return true return false ## 清理输入 static func sanitize_input(input: String) -> String: """ 清理输入字符串,移除潜在的危险字符 @param input: 输入字符串 @return: 清理后的字符串 """ var sanitized = input # 移除控制字符(除了换行和制表符) var clean_chars = [] for i in range(sanitized.length()): var char_code = sanitized.unicode_at(i) # 保留可打印字符、空格、换行、制表符,以及Unicode字符(支持中文等) if (char_code >= 32 and char_code <= 126) or char_code == 10 or char_code == 9 or char_code > 127: clean_chars.append(sanitized[i]) sanitized = "".join(clean_chars) # 移除HTML标签 sanitized = remove_html_tags(sanitized) # 限制连续空格 sanitized = limit_consecutive_spaces(sanitized) return sanitized.strip_edges() ## 移除HTML标签 static func remove_html_tags(input: String) -> String: """ 移除HTML标签 @param input: 输入字符串 @return: 移除标签后的字符串 """ var regex = RegEx.new() regex.compile("<[^>]*>") return regex.sub(input, "", true) ## 限制连续空格 static func limit_consecutive_spaces(input: String) -> String: """ 将多个连续空格替换为单个空格 @param input: 输入字符串 @return: 处理后的字符串 """ var regex = RegEx.new() regex.compile("\\s+") return regex.sub(input, " ", true) ## 验证消息格式 static func validate_message_format(message: Dictionary) -> bool: """ 验证网络消息格式的安全性 @param message: 消息字典 @return: 是否安全 """ # 基础格式检查 if not message.has("type") or not message.has("data") or not message.has("timestamp"): return false # 检查消息类型是否在允许列表中 var allowed_types = [ "auth_request", "auth_response", "character_create", "character_move", "character_state", "dialogue_send", "world_state", "ping", "pong", "error" ] if not message.type in allowed_types: return false # 检查时间戳是否合理(不能太旧或太新) var current_time = Time.get_unix_time_from_system() * 1000 # 转换为毫秒 var msg_time = message.get("timestamp", 0) var time_diff = abs(current_time - msg_time) # 允许5分钟的时间差(毫秒) if time_diff > 300000: # 5分钟 = 300000毫秒 return false # 检查数据字段 if typeof(message.data) != TYPE_DICTIONARY: return false return true ## 创建会话 func create_session(client_id: String, username: String) -> String: """ 创建新的用户会话 @param client_id: 客户端ID @param username: 用户名 @return: 会话令牌 """ var session_token = generate_session_token() var session_data = { "client_id": client_id, "username": username, "created_at": Time.get_unix_time_from_system(), "last_activity": Time.get_unix_time_from_system(), "is_active": true } active_sessions[session_token] = session_data print("Session created for user: " + username) return session_token ## 验证会话 func validate_session(session_token: String) -> bool: """ 验证会话是否有效 @param session_token: 会话令牌 @return: 是否有效 """ if not active_sessions.has(session_token): return false var session = active_sessions[session_token] var current_time = Time.get_unix_time_from_system() # 检查会话是否过期 if current_time - session.last_activity > session_timeout: invalidate_session(session_token) return false # 更新最后活动时间 session.last_activity = current_time return session.is_active ## 使会话无效 func invalidate_session(session_token: String) -> void: """ 使会话无效 @param session_token: 会话令牌 """ if active_sessions.has(session_token): var session = active_sessions[session_token] print("Session invalidated for user: " + session.get("username", "unknown")) active_sessions.erase(session_token) ## 生成会话令牌 func generate_session_token() -> String: """ 生成安全的会话令牌 @return: 会话令牌 """ var timestamp = Time.get_unix_time_from_system() var random1 = randi() var random2 = randi() var random3 = randi() # 创建更复杂的令牌 var token_data = str(timestamp) + "_" + str(random1) + "_" + str(random2) + "_" + str(random3) return token_data.sha256_text() ## 记录失败尝试 func record_failed_attempt(identifier: String) -> bool: """ 记录失败的认证尝试 @param identifier: 标识符(IP地址或客户端ID) @return: 是否应该锁定 """ var current_time = Time.get_unix_time_from_system() if not failed_attempts.has(identifier): failed_attempts[identifier] = { "count": 0, "first_attempt": current_time, "last_attempt": current_time, "locked_until": 0 } var attempt_data = failed_attempts[identifier] # 检查是否仍在锁定期 if current_time < attempt_data.locked_until: return true # 仍被锁定 # 如果距离第一次尝试超过1小时,重置计数 if current_time - attempt_data.first_attempt > 3600: attempt_data.count = 0 attempt_data.first_attempt = current_time attempt_data.count += 1 attempt_data.last_attempt = current_time # 检查是否需要锁定 if attempt_data.count >= max_failed_attempts: attempt_data.locked_until = current_time + lockout_duration print("WARNING: Client locked due to too many failed attempts: " + identifier) return true return false ## 检查是否被锁定 func is_locked(identifier: String) -> bool: """ 检查标识符是否被锁定 @param identifier: 标识符 @return: 是否被锁定 """ if not failed_attempts.has(identifier): return false var current_time = Time.get_unix_time_from_system() var attempt_data = failed_attempts[identifier] return current_time < attempt_data.locked_until ## 清除失败尝试记录 func clear_failed_attempts(identifier: String) -> void: """ 清除失败尝试记录(成功认证后调用) @param identifier: 标识符 """ if failed_attempts.has(identifier): failed_attempts.erase(identifier) ## 清理过期会话 func cleanup_expired_sessions() -> void: """清理过期的会话""" var current_time = Time.get_unix_time_from_system() var expired_tokens = [] for token in active_sessions: var session = active_sessions[token] if current_time - session.last_activity > session_timeout: expired_tokens.append(token) for token in expired_tokens: invalidate_session(token) if expired_tokens.size() > 0: print("Cleaned up %d expired sessions" % expired_tokens.size()) ## 获取安全统计 func get_security_stats() -> Dictionary: """ 获取安全统计信息 @return: 统计信息 """ return { "active_sessions": active_sessions.size(), "failed_attempts": failed_attempts.size(), "locked_clients": _count_locked_clients() } ## 计算被锁定的客户端数量 func _count_locked_clients() -> int: """计算当前被锁定的客户端数量""" var current_time = Time.get_unix_time_from_system() var locked_count = 0 for identifier in failed_attempts: var attempt_data = failed_attempts[identifier] if current_time < attempt_data.locked_until: locked_count += 1 return locked_count ## 定期清理任务 func _ready(): """初始化安全管理器""" # 初始化安全配置 _initialize_config() # 每5分钟清理一次过期会话 var cleanup_interval = SecurityConfig.get_config("session_management", "cleanup_interval", 300.0) var cleanup_timer = Timer.new() cleanup_timer.wait_time = cleanup_interval cleanup_timer.timeout.connect(cleanup_expired_sessions) cleanup_timer.autostart = true add_child(cleanup_timer) print("SecurityManager initialized with security level: " + SecurityConfig.get_security_level()) ## 初始化配置 func _initialize_config(): """从SecurityConfig初始化配置值""" session_timeout = SecurityConfig.get_config("session_management", "session_timeout", 1800.0) max_failed_attempts = SecurityConfig.get_config("session_management", "max_failed_attempts", 5) lockout_duration = SecurityConfig.get_config("session_management", "lockout_duration", 300.0)