보안 강화: DB 자격증명(AppKey, Secret) 및 세션토큰(Access Token) 암호화 저장 구현 (AES-GCM/CBC), .env 정리
This commit is contained in:
@@ -3,6 +3,8 @@ from datetime import datetime, timedelta
|
||||
from sqlalchemy import select
|
||||
from app.db.database import SessionLocal
|
||||
from app.db.models import ApiSettings
|
||||
from app.db.models import ApiSettings
|
||||
from app.core.crypto import decrypt_str, encrypt_str
|
||||
|
||||
class KisAuth:
|
||||
BASE_URL_REAL = "https://openapi.koreainvestment.com:9443"
|
||||
@@ -29,21 +31,26 @@ class KisAuth:
|
||||
if not settings_obj or not settings_obj.appKey or not settings_obj.appSecret:
|
||||
raise ValueError("KIS API Credentials not configured.")
|
||||
|
||||
# 2. Check Expiry (Buffer 10 mins)
|
||||
# 2. Check Expiry (Buffer 10 mins)
|
||||
if settings_obj.accessToken and settings_obj.tokenExpiry:
|
||||
if settings_obj.tokenExpiry > datetime.now() + timedelta(minutes=10):
|
||||
return settings_obj.accessToken
|
||||
token_dec = decrypt_str(settings_obj.accessToken)
|
||||
if token_dec and token_dec != "[Decryption Failed]":
|
||||
if settings_obj.tokenExpiry > datetime.now() + timedelta(minutes=10):
|
||||
return token_dec
|
||||
|
||||
# 3. Issue New Token
|
||||
token_data = await self._issue_token(settings_obj.appKey, settings_obj.appSecret)
|
||||
app_key_dec = decrypt_str(settings_obj.appKey)
|
||||
app_secret_dec = decrypt_str(settings_obj.appSecret)
|
||||
token_data = await self._issue_token(app_key_dec, app_secret_dec)
|
||||
|
||||
# 4. Save to DB
|
||||
settings_obj.accessToken = token_data['access_token']
|
||||
# 4. Save to DB (Encrypt Token)
|
||||
settings_obj.accessToken = encrypt_str(token_data['access_token'])
|
||||
# expires_in is seconds (usually 86400)
|
||||
settings_obj.tokenExpiry = datetime.now() + timedelta(seconds=int(token_data['expires_in']))
|
||||
|
||||
await db_session.commit()
|
||||
return settings_obj.accessToken
|
||||
return token_data['access_token']
|
||||
|
||||
except Exception as e:
|
||||
await db_session.rollback()
|
||||
@@ -83,12 +90,16 @@ class KisAuth:
|
||||
raise ValueError("KIS API Credentials not configured.")
|
||||
|
||||
if settings_obj.websocketApprovalKey:
|
||||
return settings_obj.websocketApprovalKey
|
||||
approval_key_dec = decrypt_str(settings_obj.websocketApprovalKey)
|
||||
if approval_key_dec and approval_key_dec != "[Decryption Failed]":
|
||||
return approval_key_dec
|
||||
|
||||
# Issue New Key
|
||||
approval_key = await self._issue_approval_key(settings_obj.appKey, settings_obj.appSecret)
|
||||
app_key_dec = decrypt_str(settings_obj.appKey)
|
||||
app_secret_dec = decrypt_str(settings_obj.appSecret)
|
||||
approval_key = await self._issue_approval_key(app_key_dec, app_secret_dec)
|
||||
|
||||
settings_obj.websocketApprovalKey = approval_key
|
||||
settings_obj.websocketApprovalKey = encrypt_str(approval_key)
|
||||
await db_session.commit()
|
||||
|
||||
return approval_key
|
||||
|
||||
@@ -5,6 +5,7 @@ from app.core.rate_limiter import global_rate_limiter
|
||||
from app.db.database import SessionLocal
|
||||
from app.db.models import ApiSettings
|
||||
from sqlalchemy import select
|
||||
from app.core.crypto import decrypt_str
|
||||
|
||||
class KisClient:
|
||||
"""
|
||||
@@ -51,8 +52,8 @@ class KisClient:
|
||||
headers = {
|
||||
"Content-Type": "application/json",
|
||||
"authorization": f"Bearer {token}",
|
||||
"appkey": settings.appKey,
|
||||
"appsecret": settings.appSecret,
|
||||
"appkey": decrypt_str(settings.appKey),
|
||||
"appsecret": decrypt_str(settings.appSecret),
|
||||
"tr_id": tr_id,
|
||||
"tr_cont": "",
|
||||
"custtype": "P"
|
||||
@@ -106,7 +107,7 @@ class KisClient:
|
||||
# -----------------------------
|
||||
async def get_balance(self, market: str) -> Dict:
|
||||
settings = await self._get_settings()
|
||||
acc_no = settings.accountNumber
|
||||
acc_no = decrypt_str(settings.accountNumber)
|
||||
# acc_no is 8 digits. Split? "500xxx-01" -> 500xxx, 01
|
||||
if '-' in acc_no:
|
||||
cano, prdt = acc_no.split('-')
|
||||
@@ -156,11 +157,13 @@ class KisClient:
|
||||
price: 0 for Market? KIS logic varies.
|
||||
"""
|
||||
settings = await self._get_settings()
|
||||
if '-' in settings.accountNumber:
|
||||
cano, prdt = settings.accountNumber.split('-')
|
||||
acc_no_str = decrypt_str(settings.accountNumber)
|
||||
|
||||
if '-' in acc_no_str:
|
||||
cano, prdt = acc_no_str.split('-')
|
||||
else:
|
||||
cano = settings.accountNumber[:8]
|
||||
prdt = settings.accountNumber[8:]
|
||||
cano = acc_no_str[:8]
|
||||
prdt = acc_no_str[8:]
|
||||
|
||||
if market == "Domestic":
|
||||
# TR_ID: TTT 0802U (Buy), 0801U (Sell) -> using sample 0012U/0011U
|
||||
|
||||
Reference in New Issue
Block a user