일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | |||
5 | 6 | 7 | 8 | 9 | 10 | 11 |
12 | 13 | 14 | 15 | 16 | 17 | 18 |
19 | 20 | 21 | 22 | 23 | 24 | 25 |
26 | 27 | 28 | 29 | 30 | 31 |
- DP
- BFS
- 백트래킹
- 브루트포스
- 너비우선탐색
- DynamicProgramming
- 해시
- 깊이우선탐색
- boj
- Network
- DFS
- 그리디
- dynamic programming
- HashMap
- 스프링
- 프로그래머스
- 구현
- programmers
- Backtracking
- 부분수열의합
- 해시맵
- 알고리즘
- 백준
- Algorithm
- 동적계획법
- ReactiveProgramming
- 네트워크
- Spring
- 이분탐색
- greedy
- Today
- Total
옌의 로그
단축 URL 생성기 (민감정보 노출 방지를 곁들인,,) 본문
운영 중인 서비스에서 유저가 구매한 상품을 카드형태의 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 제공 서비스 였다. (ㅋㅋ)
(동작방식)
- 사용자가 긴 URL을 입력
- 서버는 랜덤 문자열 (ex. abc123) 생성
- 이 키와 원래 URL을 매핑해 저장 (DB, Redis 등)
- 사용자는 https://my.domain/abc123 같은 짧은 링크를 받음
- 누군가 이 링크를 열면 → 원래 URL로 리디렉션
(대표적인 단축 URL 서비스)
bit.ly | 가장 유명한 단축 서비스. 클릭 수 추적도 지원 |
tinyurl.com | 오래된 서비스, 사용자 지정 키 지원 |
t.co | 트위터 전용 단축 서비스 |
naver.me | 네이버에서 제공하는 공유용 단축 링크 |
마무리하며,,,
처음에 url을 단축해달라 요청이왔을 땐 굉장히 당황스러웠다.
애초에 쿼리스트링에 정보를 넣으면 안되는거 아닌가?
-> 하지만, 인증 토큰 없이 api를 요청할 수가 없어서 이 방법밖에 없었다
식별키값만 쿼리스트링으로 넘기고, 해당 값으로 조회 api를 public으로 하나 팔까?
식별키는 노출이 되도 괜찮나?
key를 해싱한 값이 노출되면 도루묵 아닌가?
이런 생각의 과정을 거쳐, 식별키 암호화 + 해싱(단축키) + redis ttl (저장 및 만료) 방식을 사용하게 되었다 ㅎㅎ
생각해보니, 앞으로도 비슷한 일을 겪을 수 있기에 이번 기회에 한 번 정리할 수 있어서 좋았던 것 같다.
'스터디 > 기타' 카테고리의 다른 글
[Reactive Programming] Blocking I/O와 Non-Blockin I/O (1) | 2025.09.25 |
---|---|
[Reactive Programming] 리액티브 프로그래밍 & 리액티브 스트림즈 (1) | 2025.08.29 |
ENUM과 DB 매핑 전략 (feat. Enum Converter,, 코드 테이블,,) (1) | 2025.08.28 |
(충격) 지금까지 배치 서버를 써본 줄 알았는데… 아니었음 (3) | 2025.08.26 |
DDD와 Hexagonal Architecture, 상품관리 시스템에 적용해보기 (4) | 2025.08.08 |