
서버를 2대로 늘리자 로그인이 풀렸다 (Session vs Token)
사용자가 늘어나서 서버를 증설했는데, 로그인이 자꾸 풀린다는 항의가 들어왔습니다. 세션(Session) 인증의 한계와 토큰(Token, JWT) 인증으로의 전환, 그리고 Refresh Token 도입기입니다.

사용자가 늘어나서 서버를 증설했는데, 로그인이 자꾸 풀린다는 항의가 들어왔습니다. 세션(Session) 인증의 한계와 토큰(Token, JWT) 인증으로의 전환, 그리고 Refresh Token 도입기입니다.
프론트엔드 개발자가 알아야 할 4가지 저장소의 차이점과 보안 이슈(XSS, CSRF), 그리고 언제 무엇을 써야 하는지에 대한 명확한 기준.

Debug에선 잘 되는데 Release에서만 죽나요? 범인은 '난독화'입니다. R8의 원리, Mapping 파일 분석, 그리고 Reflection을 사용하는 라이브러리를 지켜내는 방법(@Keep)을 정리해봤습니다.

비트코인은 블록체인의 일부입니다. 이중 지불 문제(Double Spending), 작업 증명(PoW)과 지분 증명(PoS)의 차이, 스마트 컨트랙트, 그리고 Web 3.0이 가져올 미래까지. 개발자 관점에서 본 블록체인의 모든 것.

내 서버가 해킹당하지 않는 이유. 포트와 IP를 검사하는 '패킷 필터링'부터 AWS Security Group까지, 방화벽의 진화 과정.

서비스 초창기, 저는 세션(Session) 기반 인증을 쓰고 있었습니다.
사용자가 로그인하면 서버 메모리에 session_id를 저장하고, 사용자 브라우저 쿠키에 그 ID를 심어줬죠.
아주 전통적이고, 안전하고, 구현도 쉬웠습니다.
사용자가 늘어나서 서버를 1대에서 2대로 늘렸습니다(Scale-out). L4 스위치(로드밸런서)가 트래픽을 반반씩 나눠주게 설정했죠.
그런데 난리가 났습니다. "방금 로그인했는데 페이지 옮기니까 로그아웃됐어요!"
원인은 간단했습니다.
로드밸런서 설정을 바꿔서, "철수의 요청은 무조건 서버 A로만 보내라"고 했습니다. 문제는 서버 A가 터지면, 거기에 붙어있던 모든 사용자의 로그인이 다 풀려버립니다. 그리고 특정 서버에만 사람이 몰리면 부하 분산의 의미가 없죠.
서버 메모리 대신, Redis라는 별도의 인메모리 DB를 둬서 세션을 공유했습니다. 서버 A도, 서버 B도 Redis를 바라보니까 문제가 해결되었습니다! 하지만... Redis가 터지면 전 국민 로그아웃이라는 단일 실패 지점(SPOF)이 생겼고, 모든 요청마다 Redis를 조회하니 네트워크 비용이 들었습니다.
선배 개발자가 조언했습니다. "야, 서버가 기억하게 하지 말고, 클라이언트가 직접 증명하게 해."
이게 바로 토큰(Token) 기반 인증, 그중에서도 JWT(Json Web Token)입니다.
이제 철수가 서버 B로 가도, 서버 B는 신분증의 도장(서명)만 확인하면 됩니다. 서버끼리 데이터를 공유할 필요도, Redis를 조회할 필요도 없어졌습니다. Stateless(무상태)의 자유를 얻은 것이죠!
JWT는 aaaaa.bbbbb.ccccc 처럼 세 부분으로 나뉩니다.
{"alg": "HS256"}){"userId": 1, "role": "admin"}). 주의: 암호화된 게 아니라 Base64로 인코딩된 것뿐입니다. 누구나 풀어볼 수 있으니 비밀번호는 절대 넣지 마세요.해커가 Payload를 조작해서 admin 권한을 얻으려 해도, 서명 값이 달라지므로 서버는 "가짜 신분증이네?" 하고 쳐냅니다.
토큰을 어디에 저장할까요?
localStorage.getItem결론: 금융권 수준의 보안이 필요하면 HttpOnly Cookie를 쓰세요. 일반적인 앱은 XSS 방어책(Content Security Policy)을 잘 세우고 LocalStorage를 써도 무방하지만, 쿠키가 더 안전한 편입니다.
토큰의 단점은 유효기간입니다. 사용자가 열심히 글을 쓰고 있는데 "30분 지났으니 나가세요" 하면 화나겠죠?
슬라이딩 세션 전략:
이렇게 하면 "활동 중인 사용자"는 계속 로그인을 유지할 수 있습니다.
JWT는 너무 편했지만, 무서운 점이 하나 있었습니다. "토큰을 탈취당하면, 해커를 막을 방법이 없다."
세션이라면 서버에서 delete session 해버리면 되지만, 이미 발급된 토큰은 유효기간이 끝날 때까지 회수할 수 없습니다.
그래서 유효기간을 1년으로 했다가는 큰일 납니다. 그렇다고 5분으로 하면 사용자가 5분마다 로그인을 다시 해야 하죠.
그래서 나온 것이 Access Token + Refresh Token 조합입니다.
사용자는 평소에 Access Token을 씁니다. 만료되면? 서버에 Refresh Token을 보내서 "새 출입증 주세요"라고 합니다. 이때 서버는 DB를 확인합니다. "이 Refresh Token 아직 유효한가? 혹시 사용자가 로그아웃하거나 신고당해서 정지된 건 아닌가?"
이 방식은 JWT의 확장성(검증 시 DB 조회 X)과 세션의 제어권(갱신 시 DB 조회 O/차단 가능)을 모두 잡은 하이브리드 전략입니다.
"요즘은 다 JWT 쓴대요"라며 무지성으로 도입하지 마세요. 여러분의 서비스가 서버 1개짜리 어드민 페이지라면, 세션이 훨씬 안전하고 편합니다.
In the early days, I used Session-based Authentication.
When a user logged in, I stored a session_id in the server memory and planted it in the user's browser cookie.
It was traditional, secure, and easy to implement.
As users grew, I scaled out from 1 server to 2. I configured the L4 switch (Load Balancer) to split traffic 50/50.
Then chaos ensued. "I just logged in, clicked a page, and got logged out!"
The cause was simple.
I changed the Load Balancer config to "Always send Alice's requests to Server A". Problem: If Server A crashes, all users attached to it are logged out instantly. And if everyone flocks to Server A, load balancing is useless.
Instead of server memory, I used Redis (In-Memory DB) to share sessions. Server A and Server B both look at Redis, so the problem was solved! But... If Redis dies, the whole nation logs out. A Single Point of Failure (SPOF). And every request hitting Redis incurs network latency.
A senior developer advised: "Hey, don't make the server remember. Make the client prove themselves."
This is Token-based Authentication, specifically JWT (Json Web Token).
Now if Alice goes to Server B, Server B just checks the stamp (signature) on her ID card. No need to share data between servers, no need to query Redis. We achieved the freedom of Statelessness!
A JWT looks like aaaaa.bbbbb.ccccc. It has three parts:
{"alg": "HS256", "typ": "JWT"}).{"userId": 1, "role": "admin"}). Warning: This is just Base64 encoded, not encrypted. Anyone can read it. Never put passwords here.If a hacker changes the payload (e.g., "role": "user" -> "role": "admin"), the signature won't match, and the server will reject it.
Where should you store this token? This is the eternal debate.
localStorage.setItem).localStorage and steal the token.Verdict: For critical apps (Banking), use HttpOnly Cookies with strict SameSite policies. For general apps, LocalStorage is okay if you trust your Content Security Policy (CSP), but Cookies are generally safer against token theft.
One major UX problem with Tokens is heavy expiry. If I'm actively using the site, I shouldn't be logged out just because 30 minutes passed.
Sliding Session logic:
This keeps the user logged in as long as they are active, but kicks them out if they are idle.
JWT was convenient, but scary. "If a token is stolen, you can't stop the hacker."
With sessions, you can just delete session on the server. But an issued token cannot be revoked until it expires.
Setting expiration to 1 year is suicide. Setting it to 5 minutes annoys users endlessly.
So we use the Access Token + Refresh Token combo.
Users use Access Token normally. When it expires? Send Refresh Token to server: "Give me a new pass." Server checks DB: "Is this Refresh Token valid? Did the user log out or get banned?"
This is a hybrid strategy capturing both JWT's Scalability (No DB on verify) and Session's Control (DB check on renew/Revokable).
Security experts often say: "Don't store tokens in the browser at all." Then where? In a lightweight Node.js server that sits between React and the API.
The browser never sees the JWT. XSS is impossible because there is no token to steal. This is the Gold Standard for enterprise security.
Don't blindly use JWT just because "everyone does it." If you're building a single-server admin panel, Sessions are much safer and easier.