오늘부터 부트캠프과정이 2주일밖에 남지 않았다. (주 5일이니까 딱 10일!)
그래서 오늘부터는 과정 마무리에 집중하면서 매일 9시까지 빠이팅🙌(🤦🏻♀️)
class Solution {
public String solution(int[] food) {
// 참여 선수 2, 음식 대칭 배치
// 홀수개면 -1 개 사용
// 사용 되는 개수/2 개 배치, 대칭 배치
// 대칭 배치는 좌우반전 또는 동시에 양쪽 배치
// 동시에 양 쪽 배치가 시간 복잡도 적을 것 같음.
// 가운데 들어갈 물(0) 초기화
StringBuilder sb = new StringBuilder("0");
for (int i = food.length - 1; i > 0; i--) {
for (int j = 0; j < food[i] / 2; j++) {
sb.insert(0, i);
sb.append(i);
}
}
return sb.toString();
}
}
하지만 StringBuilder.insert()를 반복해서 사용하면 시간 복잡도가 오히려 증가한다.
문자열 중간에 값을 끼워 넣을 때마다 그 뒤의 문자들을 모두 뒤로 밀어내야 하기 때문이다.
삽입할 때마다 의 시간이 걸려, 결과적으로 의 시간 복잡도를 가지게 된다.
반면 reverse()는 이 걸리기 때문에, 전체 로직이 의 시간 복잡도로 처리될 수 있다.
class Solution {
public String solution(int[] food) {
StringBuilder sb = new StringBuilder();
// 왼쪽 선수가 먹을 음식 배치 (음식 번호 1번부터 순차적으로)
for (int i = 1; i < food.length; i++) {
for (int j = 0; j < food[i] / 2; j++) {
sb.append(i);
}
}
String leftSide = sb.toString();
sb.append(0); // 가운데 물 추가
sb.append(new StringBuilder(leftSide).reverse());
return sb.toString();
}
}
앞으로는 각 메서드의 시간복잡도를 생각하며 사용해야겠다.
배열을 사용하면, 기존 아이디어를 살리면서도 시간 복잡도를 줄일 수 있다.
class Solution {
public String solution(int[] food) {
int totalLength = 1;
for (int i = 1; i < food.length; i++) {
totalLength += (food[i] / 2) * 2;
}
char[] result = new char[totalLength];
// 양 끝 인덱스
int left = 0;
int right = totalLength - 1;
// 중앙에 물 배치
result[totalLength / 2] = '0';
// 양방향으로 음식 채우기
for (int i = 1; i < food.length; i++) {
int count = food[i] / 2;
char foodChar = (char) (i + '0');
for (int j = 0; j < count; j++) {
result[left++] = foodChar;
result[right--] = foodChar;
}
}
return new String(result);
}
}

JWT(JSON Web Token)는 대표적인 Stateless 인증 방식이다.
{
"sub": "user123",
"role": "ADMIN",
"exp": 1710000000
}
"발급된 토큰을 서버가 즉시 통제하기 어렵다"
로그아웃 즉시 반영 어려움
권한 변경 즉시 반영 어려움
탈취 시 만료 전까지 사용 가능
비밀번호 변경 후 기존 토큰 유지 가능
| 시나리오 | 상황 | 문제점 |
|---|---|---|
| 로그아웃 문제 | 1. 사용자 로그인 |
role=ADMIN 포함이후 ADMIN → USER 권한 변경 | 기존 JWT 의 role=ADMIN 유지만료 전까지 관리자 권한 사용 가능 |모든 기술은 AT, RT에 교차 적용이 가능하나, 시스템의 처리량과 보안 강도를 고려하여 활용 범위를 정함.
| 전략 | 설명 | 장점 | 단점/한계 |
|---|---|---|---|
| 블랙리스트 | 로그아웃/정지된 토큰 ID를 Redis에 저장하여 차단 대부분의 정상 유저는 리스트에 없으므로 최소한의 확인 | 즉각적인 통제권 회수. 특정 토큰만 선택적으로 차단 가능 | Stateful 의존성 |
| 토큰 버전 관리 | 유저 정보에 버전 번호를 부여하고 토큰 내 버전과 대조 | 일괄 로그아웃 가능. 모든 기기의 접속을 한 번에 끊을 때 유리 | 특정 기기만 골라서 끄는 세밀한 제어 불가능 |
| 전략 | 설명 | 장점 | 단점 |
|---|---|---|---|
| 화이트리스트 | 서버가 발급한 RT 목록만 DB에 저장하여 관리 모든 요청마다 저장소(State)를 조회해야 하므로, 블랙리스트 방식보다 서버 부하가 큼 | 서버의 완전한 통제. 허가되지 않은 갱신 시도를 원천 차단 | Stateful 의존성 |
| RTR (Rotation) | RT 사용 시마다 새 RT를 발급하고 기존 것은 폐기 | 탈취 즉시 감지. 해커와 유저의 토큰 충돌을 통해 침입 확인 가능 | 네트워크 순단 시 정상 유저도 로그아웃될 수 있는 UX 리스크 |
[1. 전통적 세션 방식] - 매 요청마다 DB(세션 저장소) 필수 방문
사용자 -> [API 서버] -> [Session DB (Redis/RDB)] -> 인증 완료 -> 결과 반환
(SessionID 전송) (매번 Read/Write 발생)
---------------------------------------------------------------------
[2. 순수 JWT 방식] - DB 방문 없음 (Stateless, 제어 불가)
사용자 -> [API 서버] (내부 서명 검증만 수행) -> 인증 완료 -> 결과 반환
(JWT 전송) (DB 조회 0회)
* 단점: 로그아웃을 해도 만료 전까지는 토큰이 살아있음
---------------------------------------------------------------------
[3. Pagely 하이브리드 방식] - 성능과 보안의 절충안
A. 일반 API 요청 (대부분의 케이스)
사용자 -> [API 서버] -> [Redis (블랙리스트 체크)] -> 인증 완료 -> 결과 반환
(AT 전송) (차단된 유저만 확인, 매우 빠름)
B. 토큰 갱신 요청 (RT 사용 시)
사용자 -> [API 서버] -> [Redis (화이트리스트+RTR)] -> 새 토큰 발급 -> 결과 반환
(RT 전송) (저장된 RT와 1:1 대조 및 교체)
위와 같은 구조적 보완책을 도입하면 일부 Stateful해지는 측면이 있지만, 로그아웃이나 RT 갱신과 같은 시점의 민감한 일부 요청일 때만 상태를 확인하기 때문에 세션 방식 수준의 보안성을 확보할 수 있다. (보안)
블랙리스트 / 화이트리스트 도입 시 Redis 조회가 추가되어 검증 비용이 증가한다. 하지만 JWT 의 진짜 가치인 Self-contained 은 유지된다. 모든 API 요청에 대해서 세션을 저장한 저장소를 한 번씩 읽어야하는 세션 방식과 비교했을 때, 한 번 발급받은 AT의 자기 완결적 데이터를 활용하기 때문에 대부분의 요청을 DB의존성 없이 빠르게 처리한다. (성능)
권한 변경 이벤트 발생 시 Auth Service Consumer 가 해당 사용자의 RT 제거,
AT 블랙리스트 등록을 수행
AT 는 요청 빈도가 높아서 성능 중심 AT = 블랙리스트
RT 는 빈도 낮고 보안 중요도가 높기 때문에 RT = 화이트리스트
