프로젝트 진행 중 별도 Web API 프로젝트에서 관리자 권한을 체크한 뒤, 다른 Web 프로젝트에서 관리자 요청들을 필터링해서 수행하고 이 권한에 따라 클라이언트 측에서도 화면 구분이 필요한 상황에 마주했다.
권한 데이터를 어떻게 저장하고 관리할지 고민하던 중, /{Area}/{Controller}/{Action} 같은 URL 형태로 수백 개의 권한을 관리해야했다.
이걸 어디에 저장해야 하지? 라는 고민부터 어떻게 관리할지 고민이 됐다.
처음에는 단순하게 생각했다. 사용자가 로그인하면 권한 목록을 Claim에 넣어서 관리하면 되지 않을까? 하지만 곧 문제를 발견했다. 권한 URL 하나가 약 4~50 bytes, 300개면 12~15KB다. 일반적인 브라주어 쿠키 제한인 4KB를 생각하면 훨씬 초과하는 수치다.
LocalStorage에 넣으면 크기 문제는 해결되지만 JavaScript로 접근 가능하다는 보안 위험이 있다.
결국 세션 을 선택했다. 이 글에서는 어떤 사고 과정을 거쳐 이 결론에 도달했는지 정리하고자 한다.
먼저 Claim이 무엇인지 명확히 해야 한다. Claim ≠ 쿠키다.
Claim은 사용자 정보의 한 조각일 뿐이다. "이름", "이메일", "역할" 같은 key-value 데이터 구조다.
var claims = new List<Claim>
{
new Claim(ClaimTypes.NameIdentifier, "12345"),
new Claim(ClaimTypes.Name, "홍길동"),
new Claim(ClaimTypes.Role, "Admin"),
new Claim("Permission", "/Area1/Controller1/Action1"),
new Claim("Permission", "/Area2/Controller2/Action2")
};
이 Claim들을 어디에 저장하느냐 가 핵심이다. 저장 위치에 따라 쿠키 인증, JWT 인증, 세션 인증으로 나뉜다.
Claim 데이터를 암호화하여 쿠키에 저장한다.
var claims = new List<Claim> { /* 수백 개 권한 */ };
var identity = new ClaimsIdentity(claims, CookieAuthenticationDefaults.AuthenticationScheme);
var principal = new ClaimsPrincipal(identity);
await HttpContext.SignInAsync(principal);
// 브라우저 쿠키: .AspNetCore.Cookies = [암호화된 Claim 데이터]
문제:
Claim을 JWT로 인코딩하여 클라이언트에 전달한다.
var claims = new List<Claim> { /* 수백 개 권한 */ };
var token = new JwtSecurityToken(claims: claims, ...);
var jwtToken = new JwtSecurityTokenHandler().WriteToken(token);
저장 위치 선택:
문제:
서버에 데이터를 저장하고, 클라이언트에는 식별자만 전달한다.
// 로그인 시
var claims = new List<Claim> { /* 수백 개 권한 */ };
HttpContext.Session.SetString("claims", JsonSerializer.Serialize(claims));
// 쿠키에는 세션 ID만 저장 (약 20 bytes)
// .AspNetCore.Session = abc123xyz
장점:
"권한 데이터를 어디에 저장할까?"
선택지:
처음에는 클라이언트 저장을 고려했다. 매번 서버에 물어볼 필요 없이 빠르게 확인할 수 있을 것 같았다.
쿠키
LocalStorage
그래서 클라이언트 저장 포기했다.
서버에 저장 후 어떻게 매번 식별하지?
=> 식별자를 클라이언트에 주고 그 식별자를 쓰면 되겠다.
A) 권한 데이터를 암호화해서 토큰으로 (JWT)
권한 300개 → JSON 직렬화 → 15KB
→ JWT 암호화 → 20KB
→ Base64 인코딩 → 약 27KB
문제: 여전히 크다. 매 요청마다 20~30KB
B) 식별자만 주고 실제 데이터는 서버 보관 (세션)
권한 300개 → 서버 메모리/Redis 저장
세션 ID 생성 → "abc123xyz" → 약 20 bytes
→ 쿠키에 저장 가능
장점:

크기 문제
| 방식 | 클라이언트 → 서버 전송량 |
|---|---|
| JWT | 매 요청마다 27KB |
| 세션 | 매 요청마다 20 bytes |
| 차이 | 1,350배 |
보안 문제
| 방식 | 데이터 위치 | 조작 가능성 |
|---|---|---|
| JWT | 클라이언트 | 디코딩 가능 (내용 볼 수 있음) |
| 세션 | 서버 | ID만 알고 내용 모름 |
실제로 각 저장소의 크기를 확인해보자.
| 저장소 | 크기 제한 | 특징 |
|---|---|---|
| 쿠키 | 4KB | 매 요청마다 자동 전송 |
| LocalStorage | 5~10MB | JavaScript 접근 가능 (XSS 위험) |
| SessionStorage | 5~10MB | 탭 종료 시 삭제, XSS 위험 동일 |
| 서버 세션 | 서버 메모리 한계 | 클라이언트는 ID만 보유 |
권한 URL 크기 계산:
권한 하나: "/Qna/QnaTicket/Update" = 23 bytes (영문 기준)
Claim 래핑: JSON 직렬화 시 약 50~60 bytes
300개 권한: 50 × 300 = 15,000 bytes = 15KB
쿠키 제한:
수백 개의 권한을 관리하는 문제를 통해 세션 기반 인증의 필요성을 깨달았다.
핵심 원칙:
| 개념 | 설명 |
|---|---|
| Claim | 데이터 구조일 뿐, 저장 위치가 중요 |
| 쿠키 인증 | 4KB 제한으로 대량 데이터 불가능 |
| JWT | 여전히 크고, 실시간 변경 불가 |
| 세션 | 서버 저장 + 식별자 전달 = 최적 |
실무에서 "왜 이 방식을 사용하는가?"를 이해하는 것이 중요하다.
단순히 "세션이 좋다더라"가 아니라, 크기 제한과 보안, 유연성의 트레이드오프를 직접 계산하고 비교해보면 자연스럽게 답이 나온다.
하지만 항상 이게 정답이다 라고 단정지을 순 없으므로 과감하게 코드를 바꾸기도 할 것 같다.