codingstairs
노트에듀라이프연락
⌕검색⌘K
koen

Navigation

  • Intro
  • Blog
  • Life

연락하기

로그인 없이도 보낼 수 있어요. 답변이 필요하면 이메일을 함께 적어 주세요.

  • 익명 폼으로 의견 남기기 →
  • ✉ warragon112@gmail.com
  • 카카오톡 오픈채팅 ↗

© 2026 codingstairs

  • 노트
  • 에듀
  • 검색
  • 라이프
  • 연락
  • 약관
  • RSS
  • GitHub
에듀›웹 보안의 기초 — JWT · OAuth · OWASP›2단계

2단계

JWT · refresh · 회전

0회 조회

JWT · refresh · 회전

세션 관리의 실질 표준. 단 서명 알고리즘 · 만료 · 블랙리스트 세 가지를 정확히 이해해야 안전.

1. JWT 구조

<base64url header>.<base64url payload>.<base64url signature>
  • header — 알고리즘 (HS256 · RS256)
  • payload — 클레임 (sub · exp · iat 등)
  • signature — 서명

payload 는 암호화 아님 · 단순 인코딩. 민감 정보 넣지 말 것.

2. HS256 vs RS256

알고리즘 키 용도
HS256 대칭 (하나) 단일 서비스 · 내부
RS256 공개/개인 마이크로서비스 · OAuth

단일 서비스는 HS256 + 32 바이트 이상 시크릿. 서비스 여러 개가 검증만 한다면 RS256.

3. 발급 (jose 라이브러리)

import { SignJWT } from "jose";

const SECRET = new TextEncoder().encode(process.env.JWT_SECRET!);

async function issueToken(userId: string) {
  return new SignJWT({ sub: userId })
    .setProtectedHeader({ alg: "HS256" })
    .setIssuedAt()
    .setExpirationTime("15m")
    .sign(SECRET);
}

4. 만료 전략 — 짧은 access + 긴 refresh

access token  → 15분
refresh token → 30일

access 가 탈취돼도 최대 15분. refresh 는 HTTP-only 쿠키로 보관, 요청 시마다 access 갱신.

async function refreshTokens(oldRefresh: string) {
  const { payload } = await jwtVerify(oldRefresh, SECRET);
  // 회전: 새 refresh 발급하고 old 를 블랙리스트
  await db.query("INSERT INTO jwt_blacklist (jti, exp) VALUES ($1, $2)", [
    payload.jti, payload.exp,
  ]);
  return {
    access: await issueToken(payload.sub),
    refresh: await issueRefresh(payload.sub),
  };
}

5. 블랙리스트 (또는 화이트리스트)

stateless JWT 의 약점: "로그아웃 즉시 무효화 불가". 해결 두 가지.

  • 블랙리스트 — 로그아웃한 jti 저장 · 검증 시 조회
  • 화이트리스트 — 발급한 모든 jti 저장 · 없으면 무효

블랙리스트가 저장 공간 적음 (로그아웃 드문 경우).

6. HTTP-only 쿠키 vs Authorization 헤더

저장 XSS CSRF 편의
HTTP-only 쿠키 안전 위험 SSR 자동
localStorage 취약 안전 CORS 설정 필요

쿠키 + sameSite=lax + CSRF 토큰 조합이 실용적.

7. 검증 미들웨어

import { jwtVerify } from "jose";

export async function requireAuth(req: Request) {
  const token = req.headers.get("authorization")?.replace("Bearer ", "")
    ?? req.cookies?.get("access_token")?.value;
  if (!token) throw new Error("unauthorized");

  const { payload } = await jwtVerify(token, SECRET);
  if (await isBlacklisted(payload.jti)) throw new Error("revoked");
  return payload;
}

모든 API 라우트 첫 줄에서 호출 또는 Next.js middleware 로 일괄 적용.

8. 자주 걸리는 자리

  • 시크릿 길이 부족 — 32 바이트 이상. openssl rand -base64 32
  • payload 에 비밀 정보 — base64 인코딩이지 암호화 아님
  • 만료 너무 길음 — access 1 시간 이상은 위험
  • 알고리즘 downgrade 공격 — {"alg": "none"} 수락 금지. 화이트리스트로
  • 블랙리스트 TTL 없음 — 만료 시점 이후 자동 삭제 필요 (테이블 비대화)

9. 운영 체크리스트

  • 시크릿 32 바이트 이상 · 환경변수
  • access 15 분 · refresh 30 일
  • refresh 회전 + 블랙리스트
  • 로그아웃 시 쿠키 삭제 + 블랙리스트 추가
  • 알고리즘 허용 목록 (HS256 만)

하고픈 말

JWT 는 "편리하지만 만료 · 회전을 안 하면 위험" 이 정확한 표현. access 짧게 · refresh 로 보상하는 구조가 기본.

Next

  • 03-oauth-state-pkce

← 1단계

위협 모델 · OWASP 요약

3단계 →

OAuth + state · PKCE