configs.py 4.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899
  1. """
  2. 客户端配置接口(客户端使用)
  3. 已调整为 POST,并使用与 /api/keys/public 相同的握手加密格式返回:
  4. 返回结构为 {"code":200,"msg":"成功","data":{"encrypted_key": "...", "encrypted_data": "...", "signature": "...", "timestamp": ..., "nonce": "..." }}
  5. 客户端需在请求中提供 Base64 的 aes_key(32 bytes)、timestamp、nonce。
  6. """
  7. from fastapi import APIRouter, Depends
  8. from pydantic import BaseModel
  9. from sqlalchemy.orm import Session
  10. import json
  11. import base64
  12. import secrets
  13. from app.database import get_db
  14. from app.models.system_config import SystemConfig
  15. from app.utils.timestamp_validator import TimestampValidator
  16. from app.config import settings
  17. # reuse AES helper and signature generator from keys module
  18. from app.api.client.keys import _aes_encrypt, _generate_signature
  19. router = APIRouter()
  20. class ConfigsRequest(BaseModel):
  21. aes_key: str
  22. timestamp: int
  23. nonce: str
  24. @router.post("")
  25. async def get_client_configs(request: ConfigsRequest, db: Session = Depends(get_db)):
  26. """获取客户端配置(供客户端使用),使用 keys/public 相同的加密握手协议"""
  27. try:
  28. # 1. 验证时间戳
  29. is_valid, err_msg = TimestampValidator.validate_timestamp(request.timestamp, db)
  30. if not is_valid:
  31. return {
  32. "code": 400,
  33. "msg": f"时间戳验证失败: {err_msg}",
  34. "timestamp": TimestampValidator.generate_timestamp()
  35. }
  36. # 2. 验证并解码客户端提供的 AES 密钥
  37. try:
  38. client_aes_key = base64.b64decode(request.aes_key)
  39. if len(client_aes_key) != 32:
  40. raise ValueError("AES密钥长度无效")
  41. except Exception as e:
  42. return {
  43. "code": 400,
  44. "msg": "无效的AES密钥格式",
  45. "timestamp": TimestampValidator.generate_timestamp()
  46. }
  47. # 3. 准备要返回的客户端配置数据
  48. client_configs = {
  49. "api_timeout_seconds": SystemConfig.get_config_value('api_timeout_seconds', 30),
  50. "client_heartbeat_interval_seconds": SystemConfig.get_config_value('client_heartbeat_interval_seconds', 30),
  51. "max_heartbeat_failures": SystemConfig.get_config_value('max_heartbeat_failures', 10),
  52. "max_network_failures": SystemConfig.get_config_value('max_network_failures', 15),
  53. }
  54. # 4. 使用随机 response AES 密钥/IV 加密实际 payload
  55. response_aes_key = secrets.token_bytes(32)
  56. response_iv = secrets.token_bytes(16)
  57. data_json = json.dumps(client_configs, ensure_ascii=False)
  58. encrypted_data = _aes_encrypt(data_json.encode('utf-8'), response_aes_key, response_iv)
  59. # 5. 使用客户端提供的 AES 密钥加密 response_aes_key + iv(便于客户端解密)
  60. key_iv_combined = response_aes_key + response_iv
  61. encrypted_key_iv = _aes_encrypt(key_iv_combined, client_aes_key, client_aes_key[16:32])
  62. # 6. 生成签名(timestamp:nonce:base64(encrypted_data))
  63. timestamp = TimestampValidator.generate_timestamp()
  64. signature_data = f"{timestamp}:{request.nonce}:{base64.b64encode(encrypted_data).decode()}"
  65. signature = _generate_signature(signature_data, settings.SECRET_KEY)
  66. # 7. 返回与 /api/keys/public 相同的格式
  67. response = {
  68. "code": 200,
  69. "msg": "成功",
  70. "data": {
  71. "encrypted_key": base64.b64encode(encrypted_key_iv).decode(),
  72. "encrypted_data": base64.b64encode(encrypted_data).decode(),
  73. "signature": signature,
  74. "timestamp": timestamp,
  75. "nonce": request.nonce
  76. }
  77. }
  78. return response
  79. except Exception as e:
  80. return {
  81. "code": 500,
  82. "msg": "系统异常,请稍后重试;若持续发生请联系客服",
  83. "timestamp": TimestampValidator.generate_timestamp()
  84. }