feat: OMS(orderlist)와 SSO 호환되도록 세션 계약 정렬

- 세션 쿠키 이름을 SESSION_COOKIE_NAME env 로 OMS 와 통일
- 로그인 성공 시 top-level user_email / user_name 키도 함께 저장
- get_user() 가 OMS 가 만든 SSO 세션(top-level email만 있는 경우)도 인정
- /login 에 ?next= 지원 (open redirect 방지 위해 같은 호스트 절대경로만 허용)
This commit is contained in:
2026-05-24 05:38:43 +09:00
parent 023ce80d59
commit e33cc8686b
2 changed files with 40 additions and 4 deletions
+2
View File
@@ -1,6 +1,8 @@
GOOGLE_CLIENT_ID=your-google-oauth-client-id.apps.googleusercontent.com
GOOGLE_CLIENT_SECRET=your-google-oauth-client-secret
# OMS(orderlist) 와 SSO 하려면 양쪽 .env 에 동일한 값으로 설정
SESSION_SECRET_KEY=replace-with-a-long-random-secret
SESSION_COOKIE_NAME=session
SESSION_COOKIE_SECURE=true
PUBLIC_BASE_URL=https://dbx.no1king.freeddns.org
CS_ORDER_URL=https://cs.example.com
+38 -4
View File
@@ -11,6 +11,9 @@ from fastapi.staticfiles import StaticFiles
from fastapi.templating import Jinja2Templates
from starlette.middleware.sessions import SessionMiddleware
# OMS(orderlist) 와 공유하는 세션 키. SessionMiddleware 의 session_cookie 도 동일 이름.
SESSION_COOKIE_DEFAULT = "session"
load_dotenv()
@@ -45,9 +48,11 @@ app = FastAPI(title="DBX 메인 페이지")
app.add_middleware(
SessionMiddleware,
secret_key=env("SESSION_SECRET_KEY", "change-this-session-secret"),
session_cookie=env("SESSION_COOKIE_NAME", SESSION_COOKIE_DEFAULT),
https_only=env("SESSION_COOKIE_SECURE", "true").lower() == "true",
same_site="lax",
max_age=60 * 60 * 8,
path="/",
)
app.mount("/static", StaticFiles(directory=str(BASE_DIR / "static")), name="static")
@@ -64,7 +69,26 @@ def public_url_for(request: Request, route_name: str) -> str:
def get_user(request: Request) -> dict[str, Any] | None:
user = request.session.get("user")
return user if isinstance(user, dict) else None
if isinstance(user, dict):
return user
# OMS 가 top-level user_email 만 채워놓은 SSO 세션도 인정
email = request.session.get("user_email")
if email:
return {
"email": str(email),
"name": request.session.get("user_name") or str(email),
"picture": "",
}
return None
def safe_next(raw: str | None) -> str:
"""Open redirect 방지: 같은 호스트의 절대 경로만 허용."""
if not raw:
return "/"
if not raw.startswith("/") or raw.startswith("//") or raw.startswith("/\\"):
return "/"
return raw
def render_template(
@@ -140,6 +164,9 @@ async def login(request: Request):
status_code=500,
)
# SSO: ?next= 로 들어온 목적지를 OAuth 콜백 뒤에 사용하기 위해 세션에 임시 보관
request.session["_post_login_next"] = safe_next(request.query_params.get("next"))
redirect_uri = public_url_for(request, "auth_google")
return await oauth.google.authorize_redirect(
request,
@@ -174,12 +201,19 @@ async def auth_google(request: Request):
status_code=403,
)
email = str(userinfo.get("email", "")).lower().strip()
name = userinfo.get("name") or email
# OMS 와 공유하는 top-level 키 (SSO 계약)
request.session["user_email"] = email
request.session["user_name"] = name
# 기존 코드/템플릿 호환용 dict
request.session["user"] = {
"email": str(userinfo.get("email", "")).lower().strip(),
"name": userinfo.get("name") or userinfo.get("email"),
"email": email,
"name": name,
"picture": userinfo.get("picture", ""),
}
return RedirectResponse(url="/", status_code=303)
next_url = safe_next(request.session.pop("_post_login_next", "/"))
return RedirectResponse(url=next_url, status_code=303)
@app.get("/logout")