All case studies
CurrentBadabomKorea Institute of Marine Science & Technology Promotion (KIMST)Jan 2026 - Present

AUTH / SSO

Building an SSO Provider for external sites

Even after migration, the OTT (Online Technology Transfer) technology-trade site still needed to let users sign in with Badabom accounts, so I built a Provider from scratch. Single-use UUID tokens are stored in the DB so everything keeps working across multiple WAS nodes, and CI-based mapping automatically reconnects accounts that were already scattered across both sites.

Integration target
OTT and similar

External partner sites

Token
UUID + DB

Single-use · 1 min expiry

Multi-WAS
Supported

DB-backed shared store

Account mapping
CI-based

Existing members auto-linked

Spring MVCiBATISUUID TokenDB session storeCORSSHA · personal CI decryptioneGovFrame

Problem

An OTT site that outlived migration, and scattered accounts

Part of the OTT (Online Technology Transfer) technology-trade system's functionality was migrated into Badabom, but the OTT site itself remained live and continued operating on its own. A new requirement came in: users had to be able to sign in there with their Badabom account. Bringing in a commercial SSO solution just for this would have been too expensive and too slow.

On top of that, the same person had often signed up on both sites under different IDs. The system saw them as separate accounts, but by CI (Connecting Information — Korea's national personal identifier), they were the same individual. If I was going to connect the logins, I had to solve the "same person, split accounts" situation at the same time.

  • External site needs to reuse Badabom authentication

    Even after migration, the OTT site stayed alive on its own, so there had to be a path to sign in there with a Badabom account.

  • The same person exists as separate accounts on both sites

    A user who had signed up separately on OTT and Badabom was the same individual by CI but had different IDs. I had to map them so the account would not look split to that user.

  • In-memory tokens break across multiple WAS nodes

    Because of the load balancer, the WAS that handled login and the WAS that received verify could be different, so keeping tokens in an in-memory Map meant "unknown token" on the other node.

Approach

Build a lightweight Provider in-house — UUID + DB + CI mapping

A commercial solution was overkill, and for now the only integration target was OTT, so it made more sense to build a lightweight Provider myself. The shape is close to OAuth2 Implicit — once login finishes on Badabom, I attach a single-use UUID token to the redirect URL and hand it back, and when OTT checks that token against the verify endpoint it gets consumed immediately so it cannot be replayed.

The key choice was storing tokens in a DB table rather than a session or memory. That way a token issued on any WAS can be verified from any other WAS, so nothing breaks in a multi-WAS environment. I handled account mapping based on CI: if the user already has an account on OTT, the existing OTT ID is returned automatically; if not, the flow continues with the Badabom ID. From the user's side, there is no "wait, which account am I supposed to use?" moment — it just flows through.

External Site

OTT

SSO Provider

Badabom

  1. 1

    /sso/login.do

    ?redirect_uri=https://ott/...

  2. 2

    Validate redirect_uri against allow-list

    Contained in ALLOWED_DOMAINS?

  3. Render login page

    Keep redirect_uri in session

    3
  4. 4

    POST login

    ID / PW authentication

  5. 5

    Look up existing OTT member by CI

    resolveUserIdByCi()

  6. 6

    INSERT UUID token into DB

    SSO_USER_TOKEN · 1 min expiry

  7. redirect(token=xxx)

    redirect_uri?token=...

    7
  8. 8

    /sso/verify.do?token=xxx

    Called from OTT backend

  9. 9

    Token SELECT + DELETE

    Single-use consumption (blocks reuse)

  10. { success: true, userId }

    JSON response

    10

Multi-WAS

DB-backed store means verification works from any node, regardless of where the token was issued

Account mapping

Links OTT and Badabom accounts via CI — prevents duplicate-account creation

Phishing defense

redirect_uri allow-list + single-use tokens

Process

Implementation steps

  1. 01

    Designed a DB-backed token store

    If tokens live in an in-memory Map, having two or more WAS nodes means the issuing and verifying nodes can differ, turning the token into "unknown." I stored the token, userId, and expiry time in a SSO_USER_TOKEN table so every WAS node references the same store. I also added a lightweight GC that sweeps expired tokens every 10 issuances.

    SsoController.java
    java
    // SsoController.java - stores tokens in the DB so multiple WAS nodes stay in sync
    public String generateToken(String userId, String userName, String email) {
    // Periodically clean up expired tokens (every 10 issuances)
    if (tokenGenerationCount.incrementAndGet() >= CLEANUP_INTERVAL) {
    cleanupExpiredTokens();
    tokenGenerationCount.set(0);
    }
     
    String token = UUID.randomUUID().toString().replace("-", "");
    long expireTime = System.currentTimeMillis() + TOKEN_EXPIRE_MS; // 1 minute
     
    Map<String, Object> params = new HashMap<>();
    params.put("token", token);
    params.put("userId", userId);
    params.put("expireTime", expireTime);
    ssoMappingDAO.insertToken(params); // stored in the DB rather than memory = shared across multiple WAS nodes
     
    return token;
    }
  2. 02

    CI-based OTT ↔ Badabom account mapping

    When the same person has signed up separately on both sites, CI (Connecting Information — Korea's national personal identifier) can identify them as the same individual. I decrypt the encrypted CI stored on Badabom, look up the OTT ID in SSO_USER_MAPPING, and return the OTT ID if a mapping exists; otherwise I return the Badabom ID as-is. A fallback makes sure login never fails when no mapping is found, so the flow is smooth for new members too.

    SsoController.java
    java
    // Resolves the user ID from the CI (Connecting Information — Korea's national personal identifier)
    // - Existing OTT member (row exists in SSO_USER_MAPPING): returns the OTT ID (links the two accounts)
    // - New member: returns the Badabom ID as-is
    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;
     
    // Look up the OTT ID in the mapping table
    String ottUserId = ssoMappingDAO.selectOttUserIdByCi(decryptedCi);
    if (StringUtils.isNotBlank(ottUserId)) {
    log.info("[SSO] Existing OTT member found - CI mapping: {} -> {}", badabomUserId, ottUserId);
    return ottUserId;
    }
    return badabomUserId;
    } catch (Exception e) {
    log.error("[SSO] CI-based ID resolution failed, falling back to Badabom ID", e);
    return badabomUserId;
    }
    }
  3. 03

    Single-use verification + CORS

    When OTT checks a token via /sso/verify.do, I delete the token from the DB immediately after verification to block reuse. Because the call comes from an external domain, I also send back the necessary CORS headers.

    SsoController.java
    java
    // Token verification API - called by OTT, single-use consumption
    @RequestMapping(value = "/sso/verify.do", method = {GET, POST})
    @ResponseBody
    public Map<String, Object> verify(HttpServletRequest request, HttpServletResponse response) {
    // Allow 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", "Token is required.");
    return result;
    }
     
    // Look up the token in the DB (includes expiry check)
    String userId = ssoMappingDAO.selectTokenUserId(token);
     
    if (StringUtils.isBlank(userId)) {
    result.put("success", false);
    result.put("message", "Invalid token.");
    return result;
    }
     
    // Single-use: delete immediately after verification (prevents reuse)
    ssoMappingDAO.deleteToken(token);
     
    result.put("success", true);
    result.put("userId", userId);
    return result;
    }
  4. 04

    redirect_uri allow-list + logging

    To defend against token theft and phishing, I only allow pre-registered domains for redirect_uri. Disallowed domains are rejected with 403, and the attempt is recorded as a warn log so it can be used for later auditing.

    SsoController.java
    java
    // Only issue tokens to allow-listed redirect_uri domains
    private static final String[] ALLOWED_DOMAINS = {
    "localhost:3000", // for testing
    "ofris.kimst.re.kr", // OTT production
    "192.168.40.36:8000" // OTT internal-network testing
    };
     
    private boolean isAllowedRedirectUri(String redirectUri) {
    for (String allowed : ALLOWED_DOMAINS) {
    if (redirectUri.contains(allowed)) return true;
    }
    return false;
    }
     
    // Validate on entry to /sso/login.do
    if (!isAllowedRedirectUri(redirectUri)) {
    log.warn("[SSO] Disallowed redirect_uri: {}", redirectUri);
    response.sendError(HttpServletResponse.SC_FORBIDDEN, "Disallowed redirect_uri");
    return;
    }

Outcome

Results and lessons

  • SSO integration with the external site is complete

    The flow — signing in on OTT with a Badabom account, receiving a token, and creating an OTT session — runs reliably even in a multi-WAS environment.

  • Accounts on both sites are automatically linked by CI

    Even when an existing OTT member separately signed up on Badabom, CI mapping recognizes them as the same user. From the user's side, the confusion of seeing their account as split simply disappeared.

  • Learned the "tokens in DB, not session" pattern

    The problem of shared state breaking in a multi-WAS environment is exactly the one I ran into with Redis session clustering. This reinforced the principle: "shared state belongs in an external store."

  • redirect_uri must always be allow-listed

    A contains-based string match looks simple but can be fragile, so for real deployments there is room to tighten host and scheme parsing. In the next iteration I plan to use a URL parser to make the validation logic stricter.

MORE

Explore other cases

Badabom

DEVOPS / OBSERVABILITY

SSE + Cross-WAS Real-Time Log Viewer

The WAS lived in the Daejeon IDC, but network-segregation policy meant only Busan-office PCs could reach it — so pulling a log effectively meant flying to Busan. I built an SSE-based viewer inside the admin web and added a cross-WAS relay so logs from both WAS nodes stream into a single screen.

View detail

Badabom

LEGACY MIGRATION

Migrating the OTT Technology-Trade System into Badabom

Moved an Oracle + MyBatis technology-trade platform (OTT) onto PostgreSQL + iBATIS. Rewrote 87 URLs, 34 JSPs, 80+ SQL queries, and 14 tables.

View detail

GAIS — Government Advertising Integrated Support System

CI/CD

Automating the Build and Deploy Pipeline

Replaced a fully manual build-and-deploy workflow with a Jenkins + GitLab Webhook pipeline, cutting deploy time from 15–20 min down to around 4 min.

View detail

GAIS — Government Advertising Integrated Support System

INFRA / SESSION

Redis-Backed Session Clustering

JEUS Standard doesn't support native session clustering, so I put Redis in front as an external session store. That unlocked rolling restarts across WAS nodes.

View detail

GAIS — Government Advertising Integrated Support System

SECURITY / NETWORK

Applying TLS 1.3 via an Nginx Reverse Proxy

Touching the shared WebtoB SSL felt risky, so I put Nginx in front and terminated TLS there instead. Existing services kept running untouched while TLS 1.3 was rolled out.

View detail

Freelance · Side Projects

CLIENT WORK / WEB

Pitched and Built a Postpartum Care Center Site Renewal

My wife had stayed at a postpartum care center whose website felt dated, so I mocked up a UI sample and pitched it myself. I built an Astro static site with a 192-frame scroll animation, Kakao Map, and SEO — then shipped it to their production domain.

View detail

Freelance · Side Projects

SIDE PROJECT / AI

Family-Driven Baby Naming with AI + Tournament-Style Voting

Existing naming services are designed for solo use, so I built a way for the whole family to join in. GPT-4o suggests names aligned with Saju (birth-chart) and Ohaeng (Five-Element) rules, and the family votes tournament-style to pick the final name.

View detail
SSO Provider Integration | Case Study