[카카오테크 부트캠프] 5/27 TIL: HTTP 자격증명 Header, JWT, Layered Architecture

주영진·2026년 5월 28일

1. HTTP 자격증명 Header

HTTP는 stateless이기 때문에, 보호된 리소스에 접근할 때마다 인증 과정이 필요하다. 이 인증 정보는 Header에 담긴다.

자격증명 헤더는 방향에 따라 두 가지로 나뉜다.

헤더방향역할
WWW-Authenticate서버 → 클라이언트인증이 필요하다고 알리는 헤더 (401과 함께 전송)
Authorization클라이언트 → 서버인증 정보를 담아 보내는 헤더

인증 흐름:

1. 클라이언트 → 서버        "이 리소스 줘"
2. 서버 → 클라이언트        401 + WWW-Authenticate: "Basic으로 인증해"
3. 클라이언트 → 서버        Authorization: Basic [인코딩된 정보]
4. 서버 → 클라이언트        인증 성공 → 리소스 응답

인증 방식 종류

방식설명
BasicID:PW를 Base64 인코딩해서 전송
DigestPW를 해시로 변환해서 전송 (Basic보다 안전)
Bearer토큰(JWT 등)을 전송
OAuth외부 서비스 인증 위임 (구글 로그인 등)

1-1. Basic Authentication

ID, PW를 Base64 인코딩해서 헤더에 담아 전송하는 가장 단순한 인증 방식이다.

user:password
    ↓ Base64 인코딩
dXNlcjpwYXNzd29yZA==
    ↓ 헤더에 담아 전송
Authorization: Basic dXNlcjpwYXNzd29yZA==

Base64는 암호화가 아니라 인코딩이다. 누구나 디코딩 가능하므로 보안용이 아닌 전송용이다.

  • HTTPS TLS 암호화 통신에서만 사용해야 함
  • 매 요청마다 서버가 ID/PW 검증 → 서버 자원 부담, 병목 지점이 될 수 있음
  • 장점: 구현 간단 → 내부용 API, 내부 시스템에 적합

1-2. Digest Authentication

Basic이 매 요청마다 PW를 헤더에 담아서 보내는 문제를 해결하기 위해 등장했다. PW 대신 해시값을 전송한다.

Basic:   user:password → Base64 인코딩 → 전송  (사실상 평문)
Digest:  password → 해시(Hash) → 전송           (원본 비밀번호 안 보냄)

핵심 개념

  • nonce — 서버가 클라이언트한테 딱 한 번 쓰고 버리라고 주는 일회성 난수. 재전송 공격 방지.
  • realm — 서버가 관리하는 보안 공간의 범위. "지금 어느 서비스에 로그인하는지" 알려주는 문자열.
1. 클라이언트 → 서버         "이 리소스 줘"
2. 서버 → 클라이언트         401 + WWW-Authenticate (realm, nonce 포함)
3. 클라이언트               nonce + realm + PW로 해시값 계산 → Authorization 헤더에 담아 전송
4. 서버                     똑같은 방식으로 해시값 계산 → 일치하면 200 OK

Hash 계산 방식:

HA1 = MD5(email:realm:password)
HA2 = MD5(HTTP메소드:URI)
최종 response = MD5(HA1:nonce:HA2)

보안이 중요한 곳에는 SHA-256 이상 권장. MD5는 취약하다.

문제: 매번 요청마다 서버가 난수 생성, 기억, 해시 계산하는 복잡한 과정이 여전히 존재한다.


1-3. Bearer Token

"이 token을 소지한 요청은 인증, 인가 된 걸로 한다"

Bearer는 소지자, 운반인이라는 뜻이다. 토큰을 들고 있는 사람은 정당한 권한이 있는 걸로 간주한다.

GET /protected/resource HTTP/1.1
Authorization: Bearer mF_9.B5f-4.1JqM

현대 웹에서 가장 많이 사용하는 이유는 ID, PW가 최초 로그인 시 한 번만 전송되기 때문이다. 이후 클라이언트는 token만 보낸다.

장점

  1. PW 보호됨
  2. 범용성 좋음
방식브라우저모바일 앱다른 도메인
Session + Cookie
Bearer Token

쿠키는 브라우저 전용이고 도메인이 다르면 안 붙지만, 토큰은 헤더에 직접 담아서 보내면 어디서든 동작한다.

  1. CSRF 방어

CSRF(Cross-Site Request Forgery)는 다른 사이트에서 요청을 위조하는 공격이다. 쿠키는 브라우저가 자동으로 붙이기 때문에 공격자 사이트에서도 쿠키가 첨부된 요청이 날아갈 수 있다. Bearer Token은 JS가 직접 헤더에 담아야 하므로 이 공격이 유효하지 않다.

단점

  • 토큰 탈취 시 매우 위험 → HTTPS 필수, 유효기간 짧게 (15분~30분)
  • 토큰 보관 까다로움
저장소취약점이유
localStorageXSS 공격JS로 직접 접근 가능
CookieCSRF 공격브라우저가 자동으로 붙임

일반 Bearer Token의 한계

매 요청마다 서버가 DB를 조회해서 토큰을 검증해야 한다. DB I/O = 서버 자원 낭비.

기존 방식개선 방향
토큰 = 그냥 열쇠 번호토큰 = 정보가 담긴 문서
서버가 DB에서 열쇠 확인서버가 문서 자체를 해독·검증
DB I/O 발생DB I/O 없음

→ JWT

인증 방식 비교

BasicDigestBearer Token
전송 정보ID/PW (Base64)해시값토큰 문자열
PW 노출사실상 노출❌ 전송 안함
서버 DB 조회매 요청매 요청매 요청 (일반 방식)
재전송 공격 방어✅ (nonce)유효기간으로 제한
현재 사용일부 내부 API거의 안 씀주류
한계보안 매우 취약복잡, MD5 취약DB 부하 → JWT로 해결

2. JWT (JSON Web Token)

애플리케이션 간의 정보 교환을 위해 설계된, 정보를 JSON 형식으로 포함하는 자체 포함형(self-contained) 토큰이다.

  • self-contained: 토큰 안에 모든 정보가 다 들어있음
  • DB 조회 없이도 서버가 토큰만 보고 "이 사람 누구인지, 유효한지" 판단 가능

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9
.
eyJlbWFpbCI6InZvaWRAbWFpbC5jb20iLCJ1c2VybmFtZSI6InZvaWQifQ
.
zKQlHcTw8MZV8Qs4GXj_TQbsFNHqUb6YsIbjxjvmWtw

HEADER . PAYLOAD . SIGNATURE

2-1. JWT 구성 요소

Header

{
  "alg": "HS256",
  "typ": "JWT"
}
  • alg: 서명에 사용한 알고리즘
  • typ: 토큰 종류

Payload

{
  "email": "void@mail.com",
  "username": "void",
  "iat": 1609239023,
  "exp": 1609242623
}
필드의미
iat (Issued At)토큰 발급 시간
exp (Expiration)토큰 만료 시간

⚠️ Payload는 평문 영역이다. 비밀번호 같은 민감 정보는 절대 넣으면 안 된다.

Signature (서명)

"이 토큰이 위조되지 않았다"를 수학적으로 보장하는 부분이다.

Base64(헤더) + Base64(페이로드) + 서버만 아는 비밀키
→ 지정된 알고리즘으로 암호화

내용이 조금이라도 수정되면 signature가 달라진다. 이를 통해 위조를 감지한다.

2-2. 사용 이유

이유설명
무상태(Stateless)서버가 세션 저장 안 해도 됨
DB 부하 없음토큰 자체를 검증하면 끝 (CPU로 처리)
확장성서버가 여러 대여도 같은 비밀키로 검증 가능
범용성모바일, 웹, 다른 도메인 모두 동작

단점

  • 무효화가 어려움 → 한 번 발급되면 서버가 강제 만료 불가
  • 네트워크 대역폭 낭비 → 토큰 내용 많을수록 길어짐, 매 API 요청마다 전송됨

2-3. Access Token & Refresh Token

JWT는 한 번 발급되면 서버가 강제 무효화할 수 없기 때문에 토큰을 두 개로 나눠서 사용한다.

구분Access TokenRefresh Token
역할API 리소스 접근 출입증Access Token 재발급용 인증서
수명15분 ~ 30분2주 ~ 1달
저장 위치메모리 또는 LocalStorage 또는 HttpOnly 쿠키Redis(서버) 또는 HttpOnly 쿠키
전송 빈도매 API 요청마다Access Token 만료 시에만
① 로그인
   클라이언트 → 서버: ID/PW
   서버 → 클라이언트: Access Token(15분) + Refresh Token(2주)

② 일반 API 요청
   Authorization: Bearer <Access Token>

③ Access Token 만료 시
   클라이언트 → 서버: Refresh Token으로 재발급 요청
   서버 → 클라이언트: 새 Access Token 발급
   (유저는 끊김 없이 사용)

Refresh Token이 탈취당하면 Access Token을 오랫동안 발급할 수 있다. JWT 구조 자체가 서버 조회를 없앴기 때문에 주인 판단도 불가하다. → RTR로 해결

2-4. RTR (Refresh Token Rotation)

"Refresh Token은 한 번 쓰면 즉시 폐기하고, 새 걸로 교체하자"

정상 흐름:

① Access Token 만료
② 유저가 Refresh Token(R1)으로 재발급 요청
③ 서버: Redis에서 R1 확인 → R1 폐기
④ 서버: 새 Access Token + 새 Refresh Token(R2) 발급
⑤ Redis에 R2 저장

탈취 감지:

① 해커가 R1으로 재발급 요청 → 서버: R1 폐기, A2/R2 발급 → 해커에게 전달
② 진짜 유저가 R1으로 재발급 요청
③ 서버: Redis에 R1 없음 → "이미 사용된 토큰 = 탈취 의심"
④ 서버: 이 유저 관련 모든 Refresh Token 전부 만료
⑤ 해커가 들고 있던 R2도 무효화 → 공격 종료

R1이 두 번 사용되는 순간 서버가 이상 감지 → 전체 세션 강제 종료

JWT 취약점 정리

취약점해결책
Payload 내용 노출Base64는 암호화 아님 → 민감 정보 절대 넣지 말 것
토큰 탈취HTTPS 필수 + 짧은 유효기간 + HttpOnly 쿠키
강제 만료 불가토큰 블랙리스트 or 세션 혼합 사용
Refresh Token 탈취RTR 적용
동시 접속 제한Refresh Token을 서버(Redis)에서 관리
토큰 크기Payload 최소화

현업에서 "완벽한 JWT 단독 사용"은 거의 없다. 서비스 보안 요구수준에 따라 Redis, 세션, RTR 등을 조합해서 쓴다.


3. Layered Architecture

"시스템을 책임과 변경 이유에 따라 여러 계층으로 분리해, 각 계층이 정해진 역할만 수행하도록 구성하는 구조"

그냥 순서대로 코드를 쭉 쓰면 SRP 원칙 위반, 중복 코드 증가, 확장성 부족, 유지보수 어려움이 발생한다.

  • 거의 모든 프로젝트는 Layered Architecture로 설계 가능
  • 흐름은 항상 Controller → Service → Repository만 가능 (역방향 없음)
  • 전체를 Layered Architecture로 짜고, 특정 파트만 다른 DB를 활용하는 식으로 발전하다가 MSA로 넘어가기도 함

3-1. 각 계층 책임

계층역할알아야 하는 것몰라도 되는 것
ControllerHTTP 요청 수신, Service에 위임, 응답 형식 결정HTTP, 요청 파라미터DB 구조
Service비즈니스 규칙 처리, 작업 순서와 흐름 결정비즈니스 로직HTTP, DB 구조
Repository데이터 조회/저장데이터 저장 방식비즈니스 규칙, HTTP

Java(객체 지향) ↔ DB(관계 지향) → 근본적인 구조 차이 존재
→ RDB 테이블 구조를 객체화한 것이 Entity (테이블 정의, column mapping, key 정의)

MVC 패턴 vs Layered Architecture

MVC 패턴Layered Architecture
관점요청이 어떻게 흘러가는가코드를 어떤 기준으로 나누는가
초점요청 처리 흐름책임 분리

실무에서는 MVC 흐름 위에 Layered Architecture를 얹어서 사용한다.

3-2. DTO

계층마다 필요한 데이터가 다르기 때문에 계층 간 전달할 데이터 구조를 정의한 것이다.

// Entity: 모든 정보 포함
public class User {
    private Long id;
    private String email;
    private String password;   // 외부에 노출하면 안 됨
    private String username;
}

// DTO: 외부에 줄 데이터만
public class UserResponseDto {
    private Long id;
    private String email;
    private String username;
    // password 제외
}

Mapper class

Entity ↔ DTO 변환 로직을 한 곳에 모아둔 클래스다.

Middleware

계층 간 데이터가 전달될 때 이를 가로채서 처리하는 역할이다.

클라이언트 요청
      ↓
  [Filter]       ← Spring 밖 (서블릿 레벨)
      ↓
  [Interceptor]  ← Spring 안, Controller 도달 전
      ↓
  Controller
FilterInterceptor
위치Spring 밖 (서블릿 레벨)Spring 안
적용 대상모든 요청Spring이 처리하는 요청만
Spring Bean 사용어려움가능
주요 사용처CORS, 인코딩, 보안JWT 인증, 로깅, 권한 확인

DTO @Valid

클라이언트가 잘못된 데이터를 보내는 경우를 DTO에서 검증 규칙으로 막는다.

public class SignupRequestDto {
    @NotBlank @Email
    private String email;

    @NotBlank @Size(min = 8)
    private String password;
}

// Controller에서 @Valid 적용
@PostMapping("/signup")
public ResponseEntity<?> signup(@RequestBody @Valid SignupRequestDto dto) {
    // 여기 도달 = 검증 통과한 데이터
}

전체 요청 흐름

클라이언트 요청
      ↓
  [Filter]      CORS, 인코딩 처리
      ↓
  [Interceptor] JWT 토큰 검증 → 실패 시 401 차단
      ↓
  Controller    @Valid로 DTO 검증 → 실패 시 400 차단
      ↓
  [Mapper]      DTO → Entity 변환
      ↓
  Service       비즈니스 로직
      ↓
  Repository    DB 접근
      ↓
  [Mapper]      Entity → DTO 변환
      ↓
클라이언트 응답
profile
'개발사(社)' (주)영진

0개의 댓글