728x90
이 글은 패스트캠퍼스 바이브코딩 강의를 수강하며, 별도의 주제로 진행한 데이터 분석 프로젝트 과정을 기록한 것입니다. 코딩과 글 작성에는 클로드코드와 커서AI를 함께 활용했음을 미리 밝힙니다.

Episode 10: 문을 여는 코드, PREM-- (The Code That Opens Doors)

"100개의 특별한 입장권. 환경을 감지하고, Secrets를 관리하며, 세션을 기억하는 프리미엄 인증 시스템."


🎯 이번 에피소드에서는

프리미엄 기능 (Premium Features)을 보호하기 위한 인증 시스템 (Authentication System)을 구축합니다.

환경 감지 (Environment Detection)부터 액세스 코드 (Access Code), Streamlit Secrets 관리까지 실전 보안 설계 (Real-world Security Design)를 배웁니다.


📚 목차

  1. 프리미엄 기능 기획
  2. 환경 감지 시스템
  3. 액세스 코드 설계
  4. Streamlit Secrets 관리
  5. 세션 상태 관리
  6. 보안 고려사항

1. 프리미엄 기능 기획 (Premium Feature Planning)

🎁 무료 vs 프리미엄 (Free vs Premium)

무료 기능 (Free Features) - 모두에게 공개

데이터 탐색 (Data Exploration):

  • 605회차 전체 당첨번호 조회
  • 번호별 출현 빈도 통계
  • 구간별/홀짝별 분석

번호 추천 (Number Recommendation):

  • 7가지 기본 전략
  • 하이브리드 추천
  • 그리드 패턴 기반 추천

번호 분석 (Number Analysis):

  • 개별 번호 상세 정보
  • 출현 이력 및 점수
  • 동반 출현 번호

예측 모델 (Prediction Model):

  • 머신러닝 학습 결과 조회
  • 패턴 분석 결과
  • 점수 기반 상위 번호

그리드 패턴 (Grid Pattern):

  • 7x7 그리드 분석
  • 위치별 출현 빈도
  • 구역별 통계

데이터 업데이트 (Data Update):

  • 텍스트 파싱
  • 자동 백업
  • CSV 업데이트

프리미엄 기능 (Premium Features) - PREM-- 필요

백테스팅 시스템 (Backtesting System):

  • 605회차 전체 백테스팅
  • 각 회차마다 직전 데이터만으로 예측
  • 실제 당첨번호와 비교 검증

가중치 최적화 (Weight Optimization):

  • Random Search + Grid Search
  • 3개 이상 일치율 최대화
  • 학습/검증 데이터 분리

성능 메트릭 (Performance Metrics):

  • 3개 이상 일치율 (3+ Match Rate)
  • 평균 일치 개수 (Average Match Count)
  • 무작위 기준선 대비 개선율
  • 통계적 유의성 테스트

고급 분석 (Advanced Analytics):

  • 회차별 예측 성공률
  • 전략별 성능 비교
  • 가중치 민감도 분석

실시간 재학습 (Real-time Retraining):

  • 최신 데이터로 모델 재학습
  • 최적 가중치 재계산
  • 즉시 추천 번호 생성

커스텀 전략 (Custom Strategies):

  • 사용자 정의 가중치
  • 전략 조합 실험
  • 성능 비교 리포트

💡 왜 프리미엄인가? (Why Premium?)

1. 계산 비용 (Computational Cost)

  • 백테스팅 1회차: 2-5초
  • 605회차 전체: 30-60분
  • Streamlit Cloud 무료 tier 부담

2. 가치 제공 (Value Proposition)

  • 통계적으로 검증된 전략
  • 무작위 대비 개선 가능성
  • 데이터 과학 학습 경험

3. 접근 제어 (Access Control)

  • 100개 한정 액세스 코드
  • 얼리 어답터 보상
  • 커뮤니티 형성

2. 환경 감지 시스템 (Environment Detection System)

🌍 로컬 vs 클라우드 (Local vs Cloud)

환경 감지 로직 (Detection Logic)

Step 1: 개발자 모드 확인 (Check Developer Mode)

import os

def is_local_dev():
    """로컬 개발 환경 확인 (Check local development)"""
    return os.getenv("LOTTO_DEV_MODE") == "true"

로컬에서 환경 변수 설정:

# macOS/Linux
export LOTTO_DEV_MODE="true"

# 또는 .bashrc / .zshrc에 추가
echo 'export LOTTO_DEV_MODE="true"' >> ~/.zshrc
source ~/.zshrc

Step 2: Streamlit Cloud 감지 (Detect Streamlit Cloud)

def is_streamlit_cloud():
    """Streamlit Cloud 환경 확인 (Check Streamlit Cloud)"""
    try:
        from streamlit.runtime import exists
        return exists()
    except ImportError:
        return False

Step 3: 통합 환경 감지 (Unified Detection)

def is_premium_unlocked():
    """프리미엄 기능 잠금 해제 여부 (Check if premium is unlocked)"""
    # 1. 로컬 개발 모드 (Local dev mode)
    if is_local_dev():
        return True

    # 2. 세션에 이미 인증됨 (Already authenticated)
    if st.session_state.get('premium_unlocked', False):
        return True

    # 3. 잠금 상태 (Locked)
    return False

🔧 환경 변수 설정 (Environment Variable Setup)

로컬 개발 (Local Development)

방법 1: 터미널에서 일회성 (Temporary in Terminal)

# 실행 전
export LOTTO_DEV_MODE="true"

# Streamlit 앱 실행
streamlit run src/web_app.py

# 확인
echo $LOTTO_DEV_MODE

방법 2: 쉘 설정 파일 (Shell Configuration)

# ~/.zshrc 또는 ~/.bashrc 편집
export LOTTO_DEV_MODE="true"

# 재로드
source ~/.zshrc

방법 3: .env 파일 (Environment File)

# .env 파일 생성
echo "LOTTO_DEV_MODE=true" > .env

# python-dotenv 사용
pip install python-dotenv
# web_app.py 상단
from dotenv import load_dotenv
load_dotenv()

Streamlit Cloud (Production)

설정 위치:

  1. Streamlit Cloud 대시보드 접속
  2. App Settings > Secrets
  3. 환경 변수 설정 안 함 (의도적으로 비활성화)
  4. 액세스 코드 필요

⚠️ 주의사항 (Important Notes)

로컬에서 프리미엄 기능 테스트 후 배포 전:

# 환경 변수 제거 (Unset variable)
unset LOTTO_DEV_MODE

# 확인 (Verify)
echo $LOTTO_DEV_MODE  # 출력 없어야 함

보안 (Security):

  • LOTTO_DEV_MODE는 로컬 전용
  • Streamlit Cloud Secrets에 절대 추가 금지
  • Git 커밋 전 .env 파일 .gitignore 추가

3. 액세스 코드 설계 (Access Code Design)

🔐 코드 형식 (Code Format)

PREM-- 구조

형식 (Format):

PREM-XXXX-XXXX

규칙 (Rules):

  • Prefix: PREM- (고정, Fixed)
  • Segment 1: 4자리 영숫자 (4-char alphanumeric)
  • Separator: - (하이픈, Hyphen)
  • Segment 2: 4자리 영숫자 (4-char alphanumeric)
  • 대소문자: 대문자만 (Uppercase only)
  • 문자 집합: A-Z, 0-9 (36자리, 36 characters)

유효한 예시 (Valid Examples):

PREM-A1B2-C3D4
PREM-QWER-TYUI
PREM-1234-5678
PREM-ABCD-EFGH
PREM-9876-5432

무효한 예시 (Invalid Examples):

prem-a1b2-c3d4      # 소문자 (lowercase)
PREM-AB-CDEF        # 세그먼트 길이 부족 (wrong length)
PREM-!@#$-%^&*      # 특수문자 (special chars)
PREM A1B2 C3D4      # 공백 (spaces)
PREMABC1234         # 구분자 없음 (no separator)

🎲 코드 생성 (Code Generation)

Python 스크립트 (Generation Script)

import random
import string

def generate_access_code():
    """액세스 코드 생성 (Generate access code)"""
    chars = string.ascii_uppercase + string.digits  # A-Z, 0-9
    segment1 = ''.join(random.choices(chars, k=4))
    segment2 = ''.join(random.choices(chars, k=4))
    return f"PREM-{segment1}-{segment2}"

def generate_bulk_codes(n=100):
    """N개 코드 일괄 생성 (Generate N codes in bulk)"""
    codes = set()
    while len(codes) < n:
        codes.add(generate_access_code())
    return sorted(codes)

# 100개 생성 (Generate 100 codes)
codes = generate_bulk_codes(100)

# TOML 형식 출력 (Print TOML format)
print("[premium]")
print("access_codes = [")
for code in codes:
    print(f'    "{code}",')
print("]")

출력 예시 (Output Example):

[premium]
access_codes = [
    "PREM-0A1B-2C3D",
    "PREM-0E4F-5G6H",
    "PREM-0I7J-8K9L",
    # ... 97 more codes
]

🔍 코드 검증 (Code Validation)

정규표현식 (Regular Expression)

import re

def validate_code_format(code):
    """코드 형식 검증 (Validate code format)"""
    # 패턴: PREM-XXXX-XXXX (Pattern)
    pattern = r'^PREM-[A-Z0-9]{4}-[A-Z0-9]{4}$'
    return re.match(pattern, code) is not None

# 테스트 (Test)
print(validate_code_format("PREM-A1B2-C3D4"))  # True
print(validate_code_format("prem-a1b2-c3d4"))  # False (소문자)
print(validate_code_format("PREM-AB-CDEF"))    # False (길이)

전체 검증 프로세스 (Full Validation Process)

def verify_access_code(input_code, valid_codes):
    """액세스 코드 검증 (Verify access code)

    Args:
        input_code: 사용자 입력 (User input)
        valid_codes: 유효한 코드 목록 (Valid codes list)

    Returns:
        bool: 검증 성공 여부 (Validation result)
    """
    # 1. 정규화 (Normalization)
    normalized = input_code.upper().strip()

    # 2. 형식 검증 (Format validation)
    if not validate_code_format(normalized):
        return False, "Invalid format"

    # 3. 코드 존재 확인 (Check existence)
    if normalized not in valid_codes:
        return False, "Code not found"

    # 4. 성공 (Success)
    return True, "Valid code"

📊 코드 통계 (Code Statistics)

총 가능한 조합 수 (Total Possible Combinations):

36^8 = 2,821,109,907,456
약 2.8조 개 (About 2.8 trillion)

100개 코드의 충돌 확률 (Collision Probability for 100 codes):

P(collision) ≈ 0.00000177% (거의 0, Nearly zero)

보안 강도 (Security Strength):

  • 무차별 대입 (Brute force): 2.8조 시도 필요
  • 100개 중 하나 맞추기: 1/100 = 1%
  • 형식까지 맞추기: 거의 불가능

4. Streamlit Secrets 관리 (Streamlit Secrets Management)

📁 로컬 Secrets (Local Secrets)

.streamlit/secrets.toml 파일 생성

1단계: 디렉토리 생성 (Create Directory)

mkdir -p .streamlit

2단계: secrets.toml 파일 작성 (Write secrets.toml)

nano .streamlit/secrets.toml

3단계: 코드 목록 추가 (Add Code List)

# .streamlit/secrets.toml

# Premium access codes
[premium]
access_codes = [
    "PREM-A1B2-C3D4",
    "PREM-QWER-TYUI",
    "PREM-1234-5678",
    "PREM-ABCD-EFGH",
    "PREM-5678-9012",
    "PREM-MNOP-QRST",
    # ... 94 more codes
]

# Total: 100 codes

4단계: Git 무시 설정 (Add to .gitignore)

# .gitignore에 추가 (Add to .gitignore)
echo ".streamlit/secrets.toml" >> .gitignore

5단계: 확인 (Verify)

git check-ignore .streamlit/secrets.toml
# 출력: .streamlit/secrets.toml (무시됨 확인)

☁️ Streamlit Cloud Secrets

클라우드 설정 방법 (Cloud Setup)

1단계: Streamlit Cloud 대시보드 접속

  1. https://share.streamlit.io/
  2. 로그인 (Login)
  3. 앱 선택 (Select app)

2단계: Secrets 페이지 이동

  1. App Settings (설정 아이콘)
  2. Secrets 탭 선택

3단계: secrets.toml 내용 복사 붙여넣기

[premium]
access_codes = [
    "PREM-A1B2-C3D4",
    "PREM-QWER-TYUI",
    "PREM-1234-5678",
    # ... (로컬과 동일한 내용)
]

4단계: Save 버튼 클릭

  • 저장 후 자동 배포 (Auto-deploy after save)
  • 앱 재시작 (App restart)

🔧 코드에서 Secrets 사용 (Using Secrets in Code)

Secrets 로드 (Loading Secrets)

import streamlit as st

def load_premium_codes():
    """프리미엄 액세스 코드 로드 (Load premium access codes)"""
    try:
        if "premium" in st.secrets:
            return st.secrets["premium"]["access_codes"]
        else:
            st.error("❌ Secrets not configured!")
            return []
    except Exception as e:
        st.error(f"❌ Error loading secrets: {e}")
        return []

에러 처리 (Error Handling)

def check_access_code(input_code):
    """액세스 코드 검증 (Check access code)"""
    # Secrets 로드 (Load secrets)
    valid_codes = load_premium_codes()

    if not valid_codes:
        return False, "Secrets not available"

    # 정규화 (Normalize)
    normalized = input_code.upper().strip()

    # 검증 (Validate)
    if normalized in valid_codes:
        return True, "Access granted"
    else:
        return False, "Invalid code"

🛡️ 보안 모범 사례 (Security Best Practices)

절대 금지 (Never Do)

코드에 직접 하드코딩 (Hardcode in code)

# 절대 안 됨! (Never do this!)
valid_codes = ["PREM-A1B2-C3D4", "PREM-QWER-TYUI"]

Git에 커밋 (Commit to Git)

# 절대 안 됨! (Never do this!)
git add .streamlit/secrets.toml
git commit -m "Add secrets"

공개 저장소에 노출 (Expose in public repo)

# README.md에 코드 공개 금지 (Don't share in README)

권장 사항 (Recommended)

Streamlit Secrets 사용 (Use Streamlit Secrets)

# 올바른 방법 (Correct way)
codes = st.secrets["premium"]["access_codes"]

.gitignore 추가 (Add to .gitignore)

.streamlit/secrets.toml
.env

로컬과 클라우드 동기화 (Sync local and cloud)

  • 로컬 파일 수정 후 클라우드 Secrets도 업데이트

접근 로그 기록 (Log access attempts)

import datetime

def log_access_attempt(code, success):
    """액세스 시도 로그 (Log access attempt)"""
    timestamp = datetime.datetime.now()
    status = "SUCCESS" if success else "FAILURE"
    print(f"[{timestamp}] {status}: {code[:8]}****")

📋 Secrets 구조 예시 (Secrets Structure Example)

# .streamlit/secrets.toml

# Premium access codes
[premium]
access_codes = [
    "PREM-0A1B-2C3D",
    "PREM-0E4F-5G6H",
    # ... 98 more codes
]

# Optional: Database credentials (예시)
[database]
host = "localhost"
port = 5432
user = "lotto_user"
password = "secure_password"

# Optional: API keys (예시)
[api]
openai_key = "sk-..."

5. 세션 상태 관리 (Session State Management)

🔄 세션 상태란? (What is Session State?)

Streamlit Session State:

  • 브라우저 탭별 독립적인 메모리 공간
  • 페이지 새로고침 시 유지
  • 탭 종료 시 삭제
  • 서버 재시작 시 초기화

사용 사례 (Use Cases):

  • 로그인 상태 유지
  • 폼 입력 데이터 보존
  • 페이지 간 데이터 전달
  • 임시 설정 저장

🚀 초기화 (Initialization)

앱 시작 시 세션 상태 초기화

import streamlit as st

def init_session_state():
    """세션 상태 초기화 (Initialize session state)"""
    # 프리미엄 잠금 상태 (Premium lock state)
    if 'premium_unlocked' not in st.session_state:
        st.session_state.premium_unlocked = False

    # 액세스 코드 입력 횟수 (Access attempts)
    if 'access_attempts' not in st.session_state:
        st.session_state.access_attempts = 0

    # 마지막 액세스 시간 (Last access time)
    if 'last_access_time' not in st.session_state:
        st.session_state.last_access_time = None

# 앱 최상단에서 호출 (Call at top of app)
init_session_state()

🔓 잠금 해제 UI (Unlock UI)

프리미엄 기능 잠금 화면

def show_premium_unlock_ui():
    """프리미엄 잠금 해제 UI (Premium unlock UI)"""
    st.title("🔒 Premium Features Locked")

    st.markdown("""
    **프리미엄 기능 (Premium Features)**에는 액세스 코드가 필요합니다.

    **포함 기능:**
    - 백테스팅 시스템 (Backtesting System)
    - 가중치 최적화 (Weight Optimization)
    - 성능 메트릭 (Performance Metrics)
    - 실시간 재학습 (Real-time Retraining)
    """)

    # 액세스 코드 입력 (Access code input)
    col1, col2 = st.columns([3, 1])

    with col1:
        code_input = st.text_input(
            "액세스 코드 (Access Code)",
            placeholder="PREM-XXXX-XXXX",
            max_chars=14,
            key="premium_code_input"
        )

    with col2:
        st.markdown("<br>", unsafe_allow_html=True)  # 정렬용
        unlock_button = st.button("🔓 Unlock", type="primary")

    # 잠금 해제 시도 (Unlock attempt)
    if unlock_button:
        if verify_premium_code(code_input):
            st.session_state.premium_unlocked = True
            st.success("✅ Premium features unlocked!")
            st.balloons()
            st.rerun()
        else:
            st.session_state.access_attempts += 1
            st.error("❌ Invalid access code. Please try again.")

            # 시도 횟수 표시 (Show attempts)
            if st.session_state.access_attempts >= 3:
                st.warning(f"⚠️ Failed attempts: {st.session_state.access_attempts}")

✅ 코드 검증 함수 (Code Verification Function)

import datetime

def verify_premium_code(input_code):
    """프리미엄 코드 검증 (Verify premium code)

    Args:
        input_code: 사용자 입력 코드 (User input code)

    Returns:
        bool: 검증 성공 여부 (Validation result)
    """
    # 1. Secrets 로드 (Load secrets)
    try:
        valid_codes = st.secrets["premium"]["access_codes"]
    except Exception as e:
        st.error(f"❌ Error loading secrets: {e}")
        return False

    # 2. 입력 정규화 (Normalize input)
    normalized = input_code.upper().strip()

    # 3. 형식 검증 (Format validation)
    import re
    pattern = r'^PREM-[A-Z0-9]{4}-[A-Z0-9]{4}$'
    if not re.match(pattern, normalized):
        return False

    # 4. 코드 존재 확인 (Check existence)
    if normalized in valid_codes:
        # 성공 로그 (Success log)
        st.session_state.last_access_time = datetime.datetime.now()
        return True
    else:
        return False

🎨 조건부 UI 렌더링 (Conditional UI Rendering)

프리미엄 상태에 따른 UI 분기

def backtesting_page():
    """백테스팅 페이지 (Backtesting page)"""
    st.title("🔬 Backtesting Results")

    # 프리미엄 확인 (Check premium)
    if not is_premium_unlocked():
        # 잠금 화면 표시 (Show lock screen)
        show_premium_unlock_ui()
        st.stop()  # 여기서 중단 (Stop here)

    # --- 프리미엄 사용자만 아래 코드 실행 ---
    # (Only premium users reach here)

    st.success("✅ Premium features unlocked!")

    # 백테스팅 UI (Backtesting UI)
    tab1, tab2, tab3 = st.tabs([
        "📊 Backtesting Results",
        "⚙️ Weight Optimization",
        "🚀 Real-time Retraining"
    ])

    with tab1:
        # 백테스팅 결과 표시
        show_backtesting_results()

    with tab2:
        # 가중치 최적화 UI
        show_weight_optimization()

    with tab3:
        # 실시간 재학습 UI
        show_realtime_retraining()

🔄 세션 재시작 (Session Rerun)

상태 변경 후 UI 갱신

# 잠금 해제 후 페이지 새로고침 (Rerun after unlock)
if st.button("🔓 Unlock"):
    if verify_premium_code(code_input):
        st.session_state.premium_unlocked = True
        st.rerun()  # 페이지 즉시 새로고침 (Immediate refresh)

🗑️ 로그아웃 기능 (Logout Feature)

세션 상태 초기화

def logout():
    """로그아웃 (Logout)"""
    # 프리미엄 상태 해제 (Clear premium state)
    st.session_state.premium_unlocked = False
    st.session_state.access_attempts = 0
    st.session_state.last_access_time = None
    st.success("✅ Logged out successfully!")
    st.rerun()

# 사이드바에 로그아웃 버튼 (Logout button in sidebar)
if st.session_state.premium_unlocked:
    if st.sidebar.button("🚪 Logout"):
        logout()

📊 세션 정보 표시 (Session Info Display)

def show_session_info():
    """세션 정보 표시 (Show session info)"""
    with st.expander("🔍 Session Information"):
        st.write(f"**Premium Status:** {'Unlocked ✅' if st.session_state.premium_unlocked else 'Locked 🔒'}")
        st.write(f"**Access Attempts:** {st.session_state.access_attempts}")

        if st.session_state.last_access_time:
            st.write(f"**Last Access:** {st.session_state.last_access_time.strftime('%Y-%m-%d %H:%M:%S')}")

        # 디버그 정보 (Debug info)
        st.write("**All Session State:**")
        st.json(dict(st.session_state))

6. 보안 고려사항 (Security Considerations)

🛡️ 보안 원칙 (Security Principles)

1. 최소 권한 원칙 (Principle of Least Privilege)

적용 사례:

  • 기본 상태: 프리미엄 잠금 (premium_unlocked = False)
  • 명시적 해제만 허용 (Explicit unlock only)
  • 세션 종료 시 자동 잠금 (Auto-lock on session end)
# 기본 잠금 (Default locked)
if 'premium_unlocked' not in st.session_state:
    st.session_state.premium_unlocked = False  # 안전한 기본값

2. 심층 방어 (Defense in Depth)

다층 보안 (Multiple layers):

Layer 1: 환경 감지 (Environment Detection)

# 로컬 vs 클라우드 구분
if is_local_dev():
    return True  # 로컬 개발자만

Layer 2: 코드 형식 검증 (Format Validation)

# 정규표현식 검증
if not re.match(r'^PREM-[A-Z0-9]{4}-[A-Z0-9]{4}$', code):
    return False

Layer 3: 코드 존재 확인 (Existence Check)

# Secrets 리스트에서 확인
if code not in valid_codes:
    return False

Layer 4: 세션 상태 검증 (Session State Verification)

# 매 요청마다 재확인
if not st.session_state.premium_unlocked:
    show_lock_screen()

3. 비밀 정보 보호 (Secret Protection)

금지 사항 (Do Not):
❌ 코드에 하드코딩
❌ Git에 커밋
❌ 로그에 출력
❌ URL 파라미터 전달
❌ 클라이언트 측 저장

권장 사항 (Recommended):
✅ Streamlit Secrets 사용
✅ 환경 변수 활용
✅ 서버 측 검증만
✅ HTTPS 통신
✅ 민감 정보 마스킹

# 로그에 마스킹 (Mask in logs)
masked_code = code[:8] + "****"
print(f"Access attempt: {masked_code}")

🚨 위협 모델 (Threat Model)

공격 시나리오 및 대응 (Attack Scenarios & Mitigations)

1. 무차별 대입 공격 (Brute Force Attack)

위협: 모든 조합 시도

대응:

# 시도 횟수 제한 (Limit attempts)
if st.session_state.access_attempts >= 10:
    st.error("❌ Too many attempts. Please try again later.")
    st.stop()

# 시도 간 지연 (Delay between attempts)
import time
time.sleep(1)  # 1초 지연

2. 코드 유출 (Code Leakage)

위협: 코드가 공개 포럼에 유출

대응:

  • 코드 1회 사용 후 만료 (One-time use)
  • 사용자 식별 및 로깅
  • 유출 시 즉시 코드 교체
# 사용 이력 기록 (Track usage)
used_codes = st.secrets.get("used_codes", [])
if code in used_codes:
    return False, "Code already used"

3. 세션 하이재킹 (Session Hijacking)

위협: 세션 쿠키 탈취

대응:

  • Streamlit은 서버 측 세션 사용 (Server-side session)
  • HTTPS 강제 (Enforce HTTPS)
  • 세션 타임아웃 설정
# 세션 타임아웃 (Session timeout)
import datetime

if st.session_state.last_access_time:
    elapsed = datetime.datetime.now() - st.session_state.last_access_time
    if elapsed.seconds > 3600:  # 1시간 (1 hour)
        st.session_state.premium_unlocked = False
        st.warning("⚠️ Session expired. Please login again.")

4. 클라이언트 측 조작 (Client-side Manipulation)

위협: 브라우저 개발자 도구로 세션 상태 조작

대응:

  • 모든 검증 서버 측에서 수행
  • 중요 작업 전 재검증
  • 클라이언트 데이터 신뢰 금지
# 중요 작업 전 재검증 (Re-verify before critical actions)
def run_backtesting():
    # 서버 측 재검증 (Server-side re-verification)
    if not verify_premium_status():
        st.error("❌ Unauthorized access")
        return

    # 백테스팅 실행 (Run backtesting)
    ...

📝 감사 로그 (Audit Log)

접근 기록 (Access Logging)

import logging
from datetime import datetime

# 로거 설정 (Setup logger)
logging.basicConfig(
    filename='premium_access.log',
    level=logging.INFO,
    format='%(asctime)s - %(levelname)s - %(message)s'
)

def log_premium_access(code, success, ip=None):
    """프리미엄 접근 로그 (Log premium access)"""
    masked_code = code[:8] + "****" if code else "None"
    status = "SUCCESS" if success else "FAILURE"

    logging.info(f"Premium access attempt: {status} | Code: {masked_code} | IP: {ip}")

    # Streamlit Cloud에서는 st.write로 대체 가능
    # (In Streamlit Cloud, can use st.write instead)

통계 대시보드 (Statistics Dashboard)

def show_access_statistics():
    """액세스 통계 표시 (Show access statistics)"""
    st.subheader("📊 Access Statistics")

    # 전체 시도 (Total attempts)
    st.metric("Total Attempts", st.session_state.access_attempts)

    # 성공률 (Success rate)
    success_rate = 100 if st.session_state.premium_unlocked else 0
    st.metric("Success Rate", f"{success_rate}%")

    # 마지막 접근 (Last access)
    if st.session_state.last_access_time:
        st.metric("Last Access", st.session_state.last_access_time.strftime('%Y-%m-%d %H:%M:%S'))

✅ 보안 체크리스트 (Security Checklist)

배포 전 확인 사항 (Pre-deployment Checklist)

  • .streamlit/secrets.toml.gitignore에 있는가?
  • Streamlit Cloud Secrets가 설정되었는가?
  • 로컬 환경 변수 LOTTO_DEV_MODE 제거했는가?
  • 액세스 코드가 코드에 하드코딩되지 않았는가?
  • 모든 검증이 서버 측에서 수행되는가?
  • 에러 메시지가 민감 정보를 노출하지 않는가?
  • HTTPS가 활성화되어 있는가? (Streamlit Cloud 자동)
  • 세션 타임아웃이 설정되었는가?
  • 로그에 민감 정보가 포함되지 않는가?
  • 프리미엄 기능이 올바르게 보호되는가?

코드 리뷰 항목 (Code Review Items)

# ✅ 올바른 예 (Correct)
if "premium" in st.secrets:
    codes = st.secrets["premium"]["access_codes"]

# ❌ 잘못된 예 (Incorrect)
codes = ["PREM-A1B2-C3D4"]  # 하드코딩 금지!
# ✅ 올바른 예 (Correct)
if normalized in valid_codes:
    st.session_state.premium_unlocked = True

# ❌ 잘못된 예 (Incorrect)
st.session_state.premium_unlocked = True  # 검증 없이!

🔗 관련 링크 (Related Links)


💬 마무리하며 (Closing Thoughts)

"PREM--". 단순한 문자열이었다. 하지만 이 14글자가 문을 여는 열쇠였다.

100개의 특별한 입장권. 각각이 백테스팅 시스템으로 가는 티켓이었다. 무차별 대입으로는 2.8조 번을 시도해야 한다. 하지만 한 개의 올바른 코드만 있으면 즉시 열린다.

환경을 감지했다. LOTTO_DEV_MODE="true"면 로컬 개발자. streamlit.runtime.exists()면 Streamlit Cloud. 두 세계를 구분하고, 각각에 맞는 규칙을 적용했다.

Streamlit Secrets. .streamlit/secrets.toml. 로컬에선 파일, 클라우드에선 대시보드. 같은 내용, 다른 형식. 절대 Git에 커밋하지 않는다. .gitignore가 지켜준다.

세션 상태. st.session_state.premium_unlocked. 브라우저 탭이 살아있는 한 기억한다. 탭을 닫으면 잊는다. 서버가 재시작되면 초기화된다. 완벽한 휘발성 메모리.

심층 방어. 환경 감지 → 형식 검증 → 코드 존재 확인 → 세션 상태 검증. 네 개의 벽. 하나만 뚫어서는 부족하다. 모두 통과해야 한다.

보안은 완벽할 수 없다. 하지만 적절할 수는 있다. 로또 분석 프로젝트에 OAuth는 과하다. 액세스 코드는 딱 맞다.

100개의 코드가 100명의 얼리 어답터를 만들었다. 특별한 사람들. 백테스팅 결과를 가장 먼저 볼 사람들.

문을 여는 코드. PREM--. 이제 당신도 그 중 한 명이 될 수 있다.


📌 SEO 태그

#포함 해시태그

#프리미엄인증 #액세스코드 #StreamlitSecrets #환경감지 #세션상태 #보안설계 #접근제어 #PREM코드 #인증시스템 #웹보안

쉼표 구분 태그

프리미엄인증, 액세스코드, Streamlit Secrets, 환경감지, 세션상태, 보안설계, 접근제어, PREM코드, 인증시스템, 웹보안, Python, Streamlit, 보안, 인증, 환경변수, 세션관리, 코드검증, 심층방어, 최소권한, 감사로그


작성: @MyJYP
시리즈: 로또 645 데이터 분석 프로젝트 (10/10) ⭐ 시리즈 완결 ⭐
라이선스: CC BY-NC-SA 4.0


📊 Claude Code 사용량

작업 전:

  • 세션 사용량: 40,244 tokens

작업 후:

  • 세션 사용량: 61,085 tokens (약 31% 사용) (17% 사용 = 71% -54%)

사용량 차이:

  • Episode 10 작성 사용량: ~20,841 tokens
  • 이미지 5개 생성 + 본문 작성 포함
  • generate_episode10_images.py 스크립트 작성 (20KB) 포함
  • 프리미엄 인증 시스템 전체 구현 포함
  • 시리즈 마지막 편 (Series Finale) - 에피소드 1~10 완료!

참고: 이 에피소드는 10편 시리즈의 마지막 편입니다. 🎉

  • 주간사용량 2%사용 (85% -83%)

🎊 시리즈 완결 기념 (Series Completion)

📖 전체 에피소드 요약

  1. Episode 1: 첫 줄의 코드, 605회의 시작 (The First Line: 605 Beginnings) https://thepin.tistory.com/194
  2. Episode 2: 숫자가 말을 걸 때 (When Numbers Speak) https://thepin.tistory.com/193
  3. Episode 3: 시간은 흐르고, 데이터는 남고 (Time Flows, Data Remains) https://thepin.tistory.com/192
  4. Episode 4: 기계가 배우는 운의 법칙 (The Machine's Fortune: Learning Rules) https://thepin.tistory.com/191
  5. Episode 5: 일곱 가지 선택의 기로(Seven Choices, One Crossroad) https://thepin.tistory.com/190
  6. Episode 6: 브라우저에 피어난 분석 (Browser-Based Analysis) https://thepin.tistory.com/189
  7. Episode 7: 8501 포트 너머로 (Beyond Port 8501) https://thepin.tistory.com/188
  8. Episode 8: 복사하고, 붙여넣고, 3초 (Copy, Paste, 3 Seconds) https://thepin.tistory.com/187
  9. Episode 9: 파일이 기억하는 순간들 (Moments Files Remember) https://thepin.tistory.com/186
  10. Episode 10: 문을 여는 코드, PREM-- (The Code That Opens Doors) https://thepin.tistory.com/185 ⭐ 현재 편

📊 시리즈 통계

  • 총 에피소드: 10편
  • 총 이미지: 50개 (각 편 5개)
  • 총 분량: 약 35,000자
  • 작업 기간: 2026-01-XX ~ 2026-01-11
  • 사용 토큰: 약 150,000+ tokens

🏆 완성된 프로젝트

로또 645 데이터 분석 프로젝트 (v6.0)

  • 605회차 전체 데이터 분석
  • 머신러닝 예측 모델
  • 7가지 추천 전략
  • Streamlit 웹 앱
  • Streamlit Cloud 배포
  • 텍스트 파싱 자동 업데이트
  • mtime 기반 동적 캐싱
  • 프리미엄 인증 시스템

GitHub: MyJYP/lotter645_1227
Live Demo: lo645251227.streamlit.app

💝 감사의 말

이 시리즈를 따라오신 모든 분들께 감사드립니다.

첫 줄의 import pandas as pd부터 마지막 PREM-****-****까지,
함께 걸어온 여정이었습니다.

로또 번호를 맞추는 것이 목표가 아니었습니다.
데이터를 읽고, 패턴을 찾고, 코드를 쓰는 과정 자체가 목표였습니다.

605회의 당첨번호가 우리에게 가르쳐준 것은
"확률의 독립성"이 아니라
"데이터 분석의 즐거움"이었습니다.

다음 프로젝트에서 또 만나요! 🚀


시리즈 완결: 2026-01-11
최종 버전: v6.0.0
에피소드: 10/10 ✅

728x90
Posted by 댕기사랑
,