AUTH / SSO
외부 사이트용 SSO Provider 구축
이관 후에도 남아 있는 OTT 사이트에서 바다봄 계정으로 로그인할 수 있도록 Provider를 직접 만들었습니다. 일회용 UUID 토큰을 DB에 저장해 다중 WAS에서도 흔들림 없이 동작하게 하고, CI 기반 매핑으로 양쪽 사이트에 이미 흩어져 있던 계정을 자동으로 연결했습니다.
- 통합 대상
- OTT 등 외부
- 토큰
- UUID + DB
- 다중 WAS
- 지원
- 계정 매핑
- CI 기반
기관 사이트
일회용 · 1분 만료
DB 기반 공유 저장
기존회원 자동 연결
Problem
이관 후에도 남는 OTT 사이트, 그리고 흩어진 계정들
OTT 기술거래 시스템의 기능 일부는 바다봄으로 이관됐지만, OTT 사이트 자체는 별도로 남아서 운영이 계속 됐습니다. 그 OTT에서 바다봄 계정으로 로그인할 수 있어야 한다는 요구가 붙었는데, 상용 SSO 솔루션을 따로 도입하기엔 비용·일정 부담이 컸습니다.
게다가 두 사이트엔 이미 같은 사람이 각자 다른 ID로 가입해 있는 경우가 많았습니다. 시스템상으로는 서로 다른 계정으로 보이지만, CI(개인 공통 식별자) 기준으로는 사실 동일인입니다. 로그인을 연결한다면 이 "같은 사람인데 계정이 쪼개져 있는" 상황도 같이 해결해줘야 했습니다.
외부 사이트에서 바다봄 인증을 재사용해야 함
이관 후에도 OTT 사이트는 따로 살아 있어서, 거기서 바다봄 계정으로 바로 로그인할 수 있는 경로가 있어야 했습니다.
같은 사람이 양쪽에 별도 계정으로 존재
OTT와 바다봄에 각각 가입한 사용자는 CI로는 동일인이지만 ID가 달랐습니다. 같은 사람인데 계정이 쪼개져 보이지 않도록 매핑해야 했습니다.
메모리 기반 토큰은 다중 WAS에서 깨짐
로드밸런서 때문에 로그인한 WAS와 verify를 받는 WAS가 달라질 수 있어서, 메모리 Map으로 토큰을 보관하면 다른 노드에서는 "없는 토큰"이 돼 버립니다.
Approach
경량 Provider 직접 구현 — UUID + DB + CI 매핑
상용 솔루션을 도입하기엔 과했고, 연동 대상도 당분간은 OTT 한 곳이라 경량 Provider를 직접 만드는 쪽이 더 맞았습니다. 형태는 OAuth2 Implicit과 비슷하게 — 바다봄에서 로그인이 끝나면 일회용 UUID 토큰을 redirect URL에 붙여 돌려주고, OTT가 그 토큰을 verify 엔드포인트로 확인하면 즉시 소비해서 재사용을 막는 흐름입니다.
토큰을 세션이나 메모리에 두지 않고 DB 테이블에 저장한 게 핵심입니다. 이렇게 하면 어느 WAS에서 발급해도 다른 WAS에서 verify가 가능하니까 다중 WAS 환경에서 깨지지 않습니다. 계정 매핑은 CI 기반으로 처리해서, OTT에 기존 계정이 있으면 자동으로 그 ID를 반환하고, 없으면 바다봄 ID로 새로 이어지도록 했습니다. 사용자는 "어느 쪽 계정이 맞는 거지?" 고민 없이 자연스럽게 넘어갈 수 있습니다.
External Site
OTT
SSO Provider
바다봄
- 1
/sso/login.do
?redirect_uri=https://ott/...
- 2
redirect_uri 화이트리스트 검증
ALLOWED_DOMAINS 포함 여부
- 3
로그인 페이지 렌더
세션에 redirect_uri 보관
- 4
POST 로그인
ID / PW 인증
- 5
CI로 OTT 기존회원 조회
resolveUserIdByCi()
- 6
UUID 토큰 DB INSERT
SSO_USER_TOKEN · 1분 만료
- 7
redirect(token=xxx)
redirect_uri?token=...
- 8
/sso/verify.do?token=xxx
OTT 백엔드에서 호출
- 9
토큰 SELECT + DELETE
일회용 소비 (재사용 차단)
- 10
{ success: true, userId }
JSON 응답
다중 WAS
DB 저장소라 어느 노드에서 발급해도 검증 가능
계정 매핑
CI로 OTT/바다봄 계정 연결 — 중복 생성 방지
피싱 방지
redirect_uri 화이트리스트 + 일회용 토큰
Process
구현 단계
- 01
DB 기반 토큰 저장소 설계
토큰을 메모리 Map에 두면 WAS가 두 대 이상일 때 발급/검증 노드가 달라 "없는 토큰"이 됩니다.
SSO_USER_TOKEN테이블에 토큰·userId·만료시각을 저장해, 모든 WAS가 같은 저장소를 참조하도록 구성했습니다. 10회 발급마다 만료 토큰을 정리하는 경량 GC도 같이 넣었습니다.SsoController.javajava// SsoController.java - 다중 WAS 대응을 위해 DB에 토큰 저장public String generateToken(String userId, String userName, String email) {// 주기적으로 만료 토큰 정리 (10회 생성마다)if (tokenGenerationCount.incrementAndGet() >= CLEANUP_INTERVAL) {cleanupExpiredTokens();tokenGenerationCount.set(0);}String token = UUID.randomUUID().toString().replace("-", "");long expireTime = System.currentTimeMillis() + TOKEN_EXPIRE_MS; // 1분Map<String, Object> params = new HashMap<>();params.put("token", token);params.put("userId", userId);params.put("expireTime", expireTime);ssoMappingDAO.insertToken(params); // ← 메모리가 아닌 DB 저장 = 다중 WAS 공유return token;} - 02
CI 기반 OTT ↔ 바다봄 계정 매핑
같은 사람이 양쪽 사이트에 별도 가입돼 있는 경우, CI(개인 공통 식별자)로 동일인을 찾을 수 있습니다. 바다봄의 암호화된 CI를 복호화해
SSO_USER_MAPPING에서 OTT ID를 조회, 매핑이 있으면 OTT ID를, 없으면 바다봄 ID를 그대로 반환합니다. 매핑이 없어도 로그인이 실패하지 않도록 fallback 처리를 두어, 신규 회원에게도 매끄럽게 동작하도록 했습니다.SsoController.javajava// CI(개인 공통 식별자) 기반 사용자 ID 결정// - OTT 기존회원(SSO_USER_MAPPING 존재): OTT ID 반환 (양쪽 계정 연결)// - 신규회원: 바다봄 ID 그대로 반환public String resolveUserIdByCi(String encryptedCi, String badabomUserId) {if (StringUtils.isBlank(encryptedCi)) return badabomUserId;try {String decryptedCi = CryptoUtil.decrypt(encryptedCi);if (StringUtils.isBlank(decryptedCi)) return badabomUserId;// 매핑 테이블에서 OTT ID 조회String ottUserId = ssoMappingDAO.selectOttUserIdByCi(decryptedCi);if (StringUtils.isNotBlank(ottUserId)) {log.info("[SSO] OTT 기존회원 발견 - CI 매핑: {} -> {}", badabomUserId, ottUserId);return ottUserId;}return badabomUserId;} catch (Exception e) {log.error("[SSO] CI 기반 ID 결정 오류, 바다봄 ID fallback", e);return badabomUserId;}} - 03
일회용 검증 + CORS
OTT가
/sso/verify.do로 토큰을 확인하면, 검증 즉시 DB에서 토큰을 삭제해 재사용을 막습니다. 외부 도메인에서 호출되므로 CORS 헤더도 같이 내려줬습니다.SsoController.javajava// 토큰 검증 API - OTT가 호출, 일회용 소비@RequestMapping(value = "/sso/verify.do", method = {GET, POST})@ResponseBodypublic Map<String, Object> verify(HttpServletRequest request, HttpServletResponse response) {// CORS 허용response.setHeader("Access-Control-Allow-Origin", "*");response.setHeader("Access-Control-Allow-Methods", "GET, POST, OPTIONS");Map<String, Object> result = new HashMap<>();String token = request.getParameter("token");if (StringUtils.isBlank(token)) {result.put("success", false);result.put("message", "토큰이 필요합니다.");return result;}// DB에서 토큰 조회 (만료 체크 포함)String userId = ssoMappingDAO.selectTokenUserId(token);if (StringUtils.isBlank(userId)) {result.put("success", false);result.put("message", "유효하지 않은 토큰입니다.");return result;}// 일회용: 검증 후 즉시 삭제 (재사용 방지)ssoMappingDAO.deleteToken(token);result.put("success", true);result.put("userId", userId);return result;} - 04
redirect_uri 화이트리스트 + 로깅
토큰 탈취·피싱 대응으로
redirect_uri는 사전에 등록된 도메인만 허용했습니다. 미허용 도메인은 403으로 거부되고, 시도 기록은 warn 로그로 남겨 이후 감사에 활용합니다.SsoController.javajava// 허용된 redirect_uri 도메인만 토큰 발급private static final String[] ALLOWED_DOMAINS = {"localhost:3000", // 테스트용"ofris.kimst.re.kr", // OTT 운영"192.168.40.36:8000" // OTT 내부망 테스트};private boolean isAllowedRedirectUri(String redirectUri) {for (String allowed : ALLOWED_DOMAINS) {if (redirectUri.contains(allowed)) return true;}return false;}// /sso/login.do 진입 시 검증if (!isAllowedRedirectUri(redirectUri)) {log.warn("[SSO] 허용되지 않은 redirect_uri: {}", redirectUri);response.sendError(HttpServletResponse.SC_FORBIDDEN, "허용되지 않은 redirect_uri");return;}
Outcome
결과와 배운 것
외부 사이트와의 SSO 통합 완료
OTT에서 바다봄 계정으로 로그인 후 토큰을 받아 OTT 세션을 생성하는 흐름이 다중 WAS 환경에서도 안정적으로 동작합니다.
양쪽 사이트 계정이 CI로 자동 연결
기존 OTT 회원이 바다봄에 별도 가입해도 CI 매핑으로 동일 사용자로 인식됩니다. 사용자 입장에서 계정이 쪼개져 보이는 혼란이 사라졌습니다.
세션이 아닌 DB에 토큰을 두는 패턴 습득
다중 WAS 환경에서 세션/메모리 기반 상태 공유가 깨지는 문제는 Redis 세션 클러스터링 때도 마주친 패턴입니다. "공유가 필요한 상태는 외부 저장소로" 원칙을 다시 확인했습니다.
redirect_uri는 무조건 화이트리스트
문자열 일치 기반의 contains 체크는 간단해 보여도 취약할 수 있어, 실제 배포 시에는 host·scheme 파싱을 더 엄격하게 다듬을 여지가 있습니다. 다음 반복에서는 URL 파서를 사용해 검증 로직을 더 타이트하게 만들 예정입니다.
MORE
다른 케이스 살펴보기
바다봄
DEVOPS / OBSERVABILITY
SSE + Cross-WAS 실시간 로그 뷰어
WAS는 대전 IDC에 있는데, 망분리 정책상 그 서버에 붙을 수 있는 PC가 부산 사무실에만 있었습니다. 그래서 로그 한 번 보려면 사실상 부산으로 가야 하는 구조였어요. 관리자 웹 안에 SSE 기반 뷰어를 만들고, 두 개 WAS 노드 로그까지 Cross-WAS 릴레이로 한 화면에서 보이도록 구성했습니다.
자세히 보기바다봄
LEGACY MIGRATION
OTT 기술거래 시스템을 바다봄으로 이관
Oracle + MyBatis 기반의 OTT 기술거래 플랫폼을 PostgreSQL + iBATIS 환경으로 옮겼습니다. 87개 URL, 34개 JSP, 80여 개 SQL과 14개 테이블을 재작성했습니다.
자세히 보기정부광고통합지원시스템
CI/CD
빌드·배포 프로세스 자동화
전부 수동으로 하던 빌드·배포를 Jenkins + GitLab Webhook 기반으로 자동화해서, 배포 시간을 15~20분에서 4분대로 줄였습니다.
자세히 보기정부광고통합지원시스템
INFRA / SESSION
Redis 기반 세션 클러스터링
JEUS Standard에서는 세션 클러스터링 기능을 못 써서, Redis를 외부 세션 저장소로 두고 우회했습니다. 덕분에 WAS 순차 재기동이 가능해졌습니다.
자세히 보기정부광고통합지원시스템
SECURITY / NETWORK
Nginx 리버스 프록시로 TLS 1.3 적용
WebtoB 공용 SSL을 건드리기 부담스러워서, 앞단에 Nginx를 세우고 거기서 TLS를 종단하도록 바꿨습니다. 기존 서비스는 영향 없이 TLS 1.3으로 올렸습니다.
자세히 보기프리랜서 · 사이드 프로젝트
CLIENT WORK / WEB
산후조리원 홈페이지 리뉴얼 제안 → 제작
아내가 입소한 조리원의 홈페이지가 오래돼 보여서 직접 UI 샘플을 만들어 제안했습니다. 192프레임 스크롤 애니메이션, 카카오맵, SEO까지 갖춘 Astro 정적 사이트를 제작해서 실제 운영 도메인으로 배포했습니다.
자세히 보기프리랜서 · 사이드 프로젝트
SIDE PROJECT / AI
AI 작명 + 이상형 월드컵으로 가족이 함께 고르는 아기 이름
기존 작명 서비스가 혼자 쓰는 구조여서, 가족이 같이 참여할 수 있는 방식을 만들었습니다. GPT-4o가 사주·오행 맞춰 이름을 추천하면, 이상형 월드컵으로 가족이 투표해서 최종 이름을 고릅니다.
자세히 보기