refs #710: remove POC4 URL remember tokens

This commit is contained in:
devmrko
2026-07-23 14:03:20 +09:00
parent c3aca16a09
commit 14b237a734
2 changed files with 67 additions and 13 deletions

View File

@@ -93,8 +93,6 @@ VPD_OPERATIONS_URL = "https://smilegate-backoffice.cloud-handson.com/"
PORTAL_AUTHENTICATED_KEY = "poc4_portal_authenticated"
PORTAL_AUTH_USER_KEY = "poc4_portal_auth_user"
PORTAL_LOGIN_FAILURE_KEY = "poc4_portal_login_failed"
PORTAL_REMEMBER_TOKEN_PARAM = "poc4_remember"
PORTAL_REMEMBER_MAX_AGE_SECONDS = 7 * 24 * 60 * 60
AUDIT_SCHEMA = "SGMP_POC"
REFERENCE_EVIDENCE_ENABLED = (
os.environ.get("POC4_REFERENCE_EVIDENCE_ENABLED", "").strip().lower()
@@ -763,27 +761,57 @@ def _restore_portal_proxy_session() -> None:
and expires_at > int(datetime.now(timezone.utc).timestamp())
and hmac.compare_digest(username, expected_username)
)
if not authenticated:
st.session_state.pop(PORTAL_AUTHENTICATED_KEY, None)
st.session_state.pop(PORTAL_AUTH_USER_KEY, None)
return
st.session_state[PORTAL_AUTHENTICATED_KEY] = True
st.session_state[PORTAL_AUTH_USER_KEY] = username
def _portal_credentials_are_valid(username: str, password: str) -> bool:
expected_username = _portal_auth_value("POC4_LOGIN_USER")
encoded_password = _portal_auth_value("POC4_LOGIN_PASSWORD_PBKDF2")
username_matches = hmac.compare_digest(username.strip(), expected_username)
password_matches = _portal_password_matches(password, encoded_password)
return username_matches and password_matches
def _render_portal_login(profile: AppProfile) -> None:
with st.container(key="console_login_container"):
render_login_brand(st, profile)
st.error(
"인증 게이트웨이의 사용자 확인 정보가 없습니다. "
"공식 포털 주소로 다시 접속해 주세요."
)
if not _portal_auth_configured():
st.info("데모 계정 설정 중입니다. 운영 담당자에게 계정 발급을 요청해 주세요.")
return
with st.form("poc4_portal_login_form", clear_on_submit=True):
username = st.text_input(
"사용자 ID",
max_chars=80,
placeholder="사용자 ID를 입력하세요.",
)
password = st.text_input(
"비밀번호",
type="password",
max_chars=200,
placeholder="비밀번호를 입력하세요.",
)
submitted = st.form_submit_button("로그인", use_container_width=True)
if submitted:
if _portal_credentials_are_valid(username, password):
st.session_state[PORTAL_AUTHENTICATED_KEY] = True
st.session_state[PORTAL_AUTH_USER_KEY] = username.strip()
st.session_state[PORTAL_LOGIN_FAILURE_KEY] = False
st.rerun()
st.session_state[PORTAL_LOGIN_FAILURE_KEY] = True
if st.session_state.get(PORTAL_LOGIN_FAILURE_KEY, False):
st.error("사용자 ID 또는 비밀번호를 확인해 주세요.")
st.markdown(
f'<p class="console-muted">{html.escape(profile.login_footer)}</p>',
unsafe_allow_html=True,
)
def _logout_portal() -> None:
st.session_state.pop(PORTAL_AUTHENTICATED_KEY, None)
st.session_state.pop(PORTAL_AUTH_USER_KEY, None)
st.session_state.pop(PORTAL_LOGIN_FAILURE_KEY, None)
st.rerun()
def _render_app_header(profile: AppProfile) -> None:
render_console_header(st, profile)
@@ -5718,7 +5746,6 @@ def main() -> None:
page_title=profile.page_title, page_icon=profile.page_icon, layout="wide"
)
_apply_console_theme(profile)
_restore_portal_proxy_session()
if not st.session_state.get(PORTAL_AUTHENTICATED_KEY, False):
_render_portal_login(profile)
return

View File

@@ -0,0 +1,27 @@
# #710 POC4 URL 로그인 토큰 제거
## 프로젝트 개요
Smilegate DATA & AI PoC의 POC4 Streamlit 콘솔은 게임 데이터 MCP와 Select AI Text2SQL 데모를 제공한다. 포털 로그인은 콘솔 접근을 보호한다.
## 문제
로그인 유지용 서명 토큰이 `poc4_remember` query parameter로 URL에 포함됐다. URL은 브라우저 기록, 프록시 로그, 공유 링크, Referrer에 남을 수 있으므로 인증 정보를 전달하는 경로로 사용하면 안 된다.
## 조치 설계
1. Streamlit 코드에서 URL 토큰 생성·검증·삭제를 모두 제거한다.
2. 로그인 상태는 현재 Streamlit 브라우저 세션에서만 유지한다. 서버가 `HttpOnly`, `Secure`, `SameSite` cookie를 발급하는 전용 인증 경로가 마련되기 전에는 영구 로그인 기능을 제공하지 않는다.
3. `POC4_LOGIN_REMEMBER_SECRET`을 교체해 기존 서명 링크를 무효화한다.
4. Caddy가 기존 `poc4_remember` query 요청을 애플리케이션으로 전달하지 않고 `https://smilegate.cloud-handson.com/`으로 303 redirect한다.
## 검증 기준
- `mcp_discovery_ui.py``poc4_remember` 또는 `st.query_params` 로그인 토큰 코드가 없다.
- 기존 query URL 요청은 query가 없는 루트 URL로 303 응답한다.
- `smilegate-poc4-console.service`가 정상 기동한다.
- 토큰, password hash, signing key는 Git·Redmine·명령 출력에 기록하지 않는다.
## 후속 개선
영구 로그인 요구가 다시 생기면 POC4 자체가 아닌 서버 인증 endpoint가 `HttpOnly; Secure; SameSite=Lax` cookie를 발급하고, Streamlit은 요청 cookie의 서버 검증 결과만 읽는 구조로 구현한다.