옌의 로그

단축 URL 생성기 (민감정보 노출 방지를 곁들인,,) 본문

스터디/기타

단축 URL 생성기 (민감정보 노출 방지를 곁들인,,)

dev-yen 2025. 9. 27. 00:32

운영 중인 서비스에서 유저가 구매한 상품을 카드형태의 ui로 예쁘게.. 웹페이지로 제공하는 기능이 있었는데, 해당 페이지는 링크로 외부에 공유할 수 있어야 했다.

 

해당 페이지에서 사용되는 정보를 받는 api는 public으로 열 수 없는 정보들이라, url 생성시 쿼리스트링으로 필요데이터를 다 박는 형태로 처음에 구현이되었었다.

https://ourdomain.com/share?userName=micky&productId=5&color=#9e8c8b&,,, // 등등 무지 길다

(url 생성은 FE쪽에서 처리한 부분이라 이런식으로 구현되어있는지 몰랐다가, url 이 너무 길다는 qa 티켓이 올라와서 나도 같이 보게되었다. ㅋㅋ)

 

문제점

민감 정보 노출

  • userName, productId와 같은 정보가 URL에 그대로 노출
  • 브라우저 히스토리, SNS 공유 시 완전히 공개됨
  • 유저 정보 도용/유출 위험

URL 길이 문제

  • 공유할 데이터가 많아질수록 URL 길어짐
  • 웹뷰에서 깨지는 현상 발생
  • SNS에서 줄임표(...)로 잘리는 경우도 있음

 

해결 방향

  • 유저나 상품 정보가 외부에 드러나지 않아야
  • SNS/웹뷰 등 다양한 환경에서 짧고 깔끔하게 공유되어야 함
  • 공유 링크는 일정 시간이 지나면 자동 만료되어야 함

단축 URL + 암호화 + Redis TTL 조합

흐름은 아래와 같다

민감 정보 → JSON 직렬화 → AES 암호화 → 단축 키 생성 → Redis 저장 → 단축 URL 제공

→ 유저는 짧은 URL만 받지만, 서버에서는 암호화된 정보를 Redis에서 찾아 처리

 

 

!민감 정보 암호화하기!

{
  "userName": "micky",
  "productId": 5
}

위 데이터를 AES로 암호화하여 Redis에 저장하고, 단축 키를 URL에 포함한다

https://ourdomain.com/s/AbC123xZ

 

 

AES 암호화 유틸

public class CryptoUtil {
    private static final String SECRET = "1234567890123456"; // 16바이트 키

    public static String encrypt(String plainText) throws Exception {
        Cipher cipher = Cipher.getInstance("AES");
        SecretKeySpec key = new SecretKeySpec(SECRET.getBytes(), "AES");
        cipher.init(Cipher.ENCRYPT_MODE, key);
        return Base64.getUrlEncoder().encodeToString(cipher.doFinal(plainText.getBytes()));
    }

    public static String decrypt(String encrypted) throws Exception {
        Cipher cipher = Cipher.getInstance("AES");
        SecretKeySpec key = new SecretKeySpec(SECRET.getBytes(), "AES");
        cipher.init(Cipher.DECRYPT_MODE, key);
        return new String(cipher.doFinal(Base64.getUrlDecoder().decode(encrypted)));
    }
}

 

Base62 단축 키 생성기

private String generateBase62Key() {
    String base62 = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
    StringBuilder sb = new StringBuilder();
    Random r = new SecureRandom();
    for (int i = 0; i < 8; i++) {
        sb.append(base62.charAt(r.nextInt(base62.length())));
    }
    return sb.toString();
}

 

 

Redis 저장소 (TTL 설정 포함)

@Service
@RequiredArgsConstructor
public class RedisShortUrlService {

    private final RedisTemplate<String, String> redisTemplate;

    public void save(String key, String encrypted, Duration ttl) {
        redisTemplate.opsForValue().set(key, encrypted, ttl);
    }

    public String get(String key) {
        return redisTemplate.opsForValue().get(key);
    }
}

(꼭 redis를 쓰지 않아도 되지만, ttl을 통해 자동만료가 가능하고 메모리 기반이라 성능이 우수하므로 사용하면 좋다 : )

 

 

단축 URL 생성 API

public String createShortUrl(String userName, Long productId) {
    String json = "{\"userName\":\"" + userName + "\",\"productId\":" + productId + "}";
    String encrypted = CryptoUtil.encrypt(json);
    String key = generateBase62Key();
    redisShortUrlService.save(key, encrypted, Duration.ofHours(24));
    return "https://ourdomain.com/s/" + key;
}

 

 

공유 링크 접근 처리

@RestController
@RequiredArgsConstructor
public class ShareController {

    private final RedisShortUrlService redisService;

    @GetMapping("/s/{key}")
    public ResponseEntity<?> handleShare(@PathVariable String key) {
        String encrypted = redisService.get(key);
        if (encrypted == null) {
            return ResponseEntity.status(HttpStatus.GONE).body("링크가 만료되었어요.");
        }

        try {
            String decrypted = CryptoUtil.decrypt(encrypted);
            // JSON 파싱 → 상품/유저 정보 조회
            return ResponseEntity.ok("공유 페이지 렌더링");
        } catch (Exception e) {
            return ResponseEntity.badRequest().body("복호화 실패");
        }
    }
}

 


 

 

단축 URL 서비스?

몰랐는데, bit.ly가 이런 단축 URL 제공 서비스 였다. (ㅋㅋ)

 

(동작방식)

  1. 사용자가 긴 URL을 입력
  2. 서버는 랜덤 문자열 (ex. abc123) 생성
  3. 이 키와 원래 URL을 매핑해 저장 (DB, Redis 등)
  4. 사용자는 https://my.domain/abc123 같은 짧은 링크를 받음
  5. 누군가 이 링크를 열면 → 원래 URL로 리디렉션

 

(대표적인 단축 URL 서비스)

bit.ly 가장 유명한 단축 서비스. 클릭 수 추적도 지원
tinyurl.com 오래된 서비스, 사용자 지정 키 지원
t.co 트위터 전용 단축 서비스
naver.me 네이버에서 제공하는 공유용 단축 링크

 

 

 

마무리하며,,,

처음에 url을 단축해달라 요청이왔을 땐 굉장히 당황스러웠다.

 

애초에 쿼리스트링에 정보를 넣으면 안되는거 아닌가?

-> 하지만, 인증 토큰 없이 api를 요청할 수가 없어서 이 방법밖에 없었다

 

식별키값만 쿼리스트링으로 넘기고, 해당 값으로 조회 api를 public으로 하나 팔까?

식별키는 노출이 되도 괜찮나?

key를 해싱한 값이 노출되면 도루묵 아닌가?

 

이런 생각의 과정을 거쳐, 식별키 암호화 + 해싱(단축키) + redis ttl (저장 및 만료) 방식을 사용하게 되었다 ㅎㅎ

 

생각해보니, 앞으로도 비슷한 일을 겪을 수 있기에 이번 기회에 한 번 정리할 수 있어서 좋았던 것 같다.

Comments