일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | |||
5 | 6 | 7 | 8 | 9 | 10 | 11 |
12 | 13 | 14 | 15 | 16 | 17 | 18 |
19 | 20 | 21 | 22 | 23 | 24 | 25 |
26 | 27 | 28 | 29 | 30 | 31 |
- 백준 1647번 도시 분할 계획 - java
- 백준 3190번
- 백준 1043번 거짓말 - java 분리 집합
- 프로그래머스 자바
- 코틀린기초
- HashMap
- 백준 1197번 최소 스패닝 트리 - java
- 백준 14938번 서강그라운드
- StringBuilder
- 프로그래머스 java
- 18111번 마인크래프트 - java 구현
- mysql hy000 에러
- dp
- append
- kotlin
- hash
- 프로그래머스
- StringTokenizer
- Java
- replace()
- Stack
- 백준 1541
- 백준 1806번 부분합 java
- ac 5430번
- 백준 2467번 용액 자바 - 이분탐색
- map
- 최소 힙 1927
- 백준 2473번 세 용액 - java
- HashSet
- toUpperCase
- Today
- Total
말하는 컴공감자의 텃밭
JWT로 관리하는 사용자 인증 - Json Web Token 본문
이번에 사이드 웹 프로젝트를 진행하면서,
회원가입이랑 인증 인가 부분을 맡으면서 JWT를 써서 로그인을 구현했는데~~ 정리해보려 한다.그만 놀아 ㅋ쿠ㅜ
간단하게 JWT 정의, 사용하는 이유, 구조, 이번 프로젝트에 진행한 코드로 정리하려 한다.
1. JWT란 무엇인가?
JWT(Json Web Token)는 JSON 형식의 정보를 저장하고 전달하기 위한 토큰이다. 보통 사용자 인증을 위해 사용되며, URL, 헤더, 혹은 쿠키에 포함되어 서버와 클라이언트 간에 안전하게 정보를 교환할 수 있다.
JWT는 세 가지 주요 구성요소로 이루어져 있다:
- Header: 토큰의 타입과 사용된 알고리즘 정보를 포함.
- Payload: 사용자 정보와 기타 데이터가 담긴 부분.
- Signature: 토큰의 위변조를 방지하기 위해 생성된 서명.
2. 그럼 왜 JWT를 사용하는가?
JWT는 인증 시스템에서 다음과 같은 이유로 많이 사용된다.
- Stateless: 서버가 클라이언트 상태를 저장할 필요가 없어 확장성이 뛰어나다.
- 효율성: 필요한 정보는 토큰에 포함되므로 DB 조회를 최소화.
- 표준화: JWT는 RFC 7519 표준을 따르며 다양한 플랫폼과 언어에서 사용 가능.
기존 세션의 경우 서버에 보안 및 데이터를 관리하는 구조이고,
토큰을 사용하면 state-less 하게 토큰 자체로 보안 및 데이터를 관리 할 수 있다.
3. JWT의 구조
앞서 다뤘듯 JWT는 Header.Payload.Signature 형태의 문자열로 구성된다.
3.1 Header
{
"alg": "HS256",
"typ": "JWT"
}
- alg: 서명 알고리즘, 일반적으로 HMAC SHA256을 사용한다.
- typ: 토큰 타입(JWT).
3.2 Payload
Payload에는 클레임(Claim)이라는 데이터가 포함된다. 클레임은 두 가지로 나뉜다:
- 등록된 클레임: iss, sub, exp 등 표준화된 키.
- 사용자 정의 클레임: 특정 비즈니스 로직에 필요한 사용자 정보.
EX)
{
"sub": "1234567890",
"name": "Gam za",
"admin": true
}
3.3 Signature
Signature는 Header와 Payload를 조합한 후 비밀키로 암호화한 값이다.
HMACSHA256(
base64UrlEncode(header) + "." + base64UrlEncode(payload),
secret
)
이런 조합으로 JWT를 생성하게 된다.
이번 프로젝트에서는 필요한것들을 추가해 JWT를 생성해 주는 JwtTokenProvider을 구성해주었다.
사용자 식별, 발행 시간, 만료 시간를 추가로 넣어주어 관리했다.
4. JWT의 안전성
JWT는 다음과 같은 방식으로 안전성을 제공한다:
- 서명(Signature): Header와 Payload를 기반으로 생성된 Signature는 토큰의 무결성을 보장한다. 즉, 누군가 Payload를 변경하면 Signature가 유효하지 않게 된다.
- 만료 시간(Expiration): exp 클레임을 사용하여 토큰의 유효 기간을 제한할 수 있다.
- 비밀키: 서버는 비밀키를 사용해 토큰을 생성하며, 클라이언트는 이를 확인할 수 없다.
하지만 JWT 자체는 암호화되지 않으므로, 민감한 데이터를 포함하면 안 된다. 보안 강화를 위해 HTTPS를 반드시 사용해야 한다. 이번 프로젝트에서 역시 기존에 JWT 자체를 프론트에게 전달하다가 완성된 이후로는 리프레시 토큰만 HTTPS 쿠키에 담아 secure 설정을 마친 후 전달했다.
5. 기존 방식의 취약점
JWT를 사용하기 전에는 세션 기반 인증이 일반적이었다. 세션 방식의 주요 문제는 다음과 같다:
- 세션 탈취:
- 쿠키가 탈취되면 공격자가 세션을 가로챌 수 있다.
- CSRF(Cross-Site Request Forgery) 공격에 취약.
- 서버 부하:
- 서버가 세션 상태를 유지해야 하므로, 많은 사용자를 처리할수록 부하가 증가.
*CSRF(Cross-Site Request Forgery)는 사용자가 인증된 상태에서 공격자가 악의적인 요청을 서버에 보내는 공격이다.
예시로 사용자가 은행 사이트에 로그인한 상태에서 악성 스크립트가 포함된 웹페이지를 방문하면, 공격자가 사용자의 세션을 이용해 계좌 이체 요청을 보낼 수 있다.
JWT는 클라이언트에 상태를 저장하기 때문에 이러한 문제를 해결한다.
하지만 JWT도 탈취될 경우 위험하므로, 다음과 같은 추가적인 보안 대책이 필요하다
여러 방법이 있는데
-
- 쿠키에 Secure 속성을 설정하여 HTTPS를 통해서만 전송되도록 제한.
- HttpOnly 속성을 사용하여 클라이언트 스크립트에서 쿠키에 접근하지 못하도록 설정.
- CSRF 토큰을 사용하여 요청의 출처를 검증.
으로 방지 및 해결을 한다.
CSRF 토큰의 경우 서버에서 발급하고, 클라이언트가 동작했을때 확인하는 용도로 사용이 된다.
기본적으로 세션마다 고유값을 담아 식별하는데, 현재 나는 세션리스로 API 서버를 구성했기에 다른 방법을 사용했다.
Spring Security의 CookieCsrfTokenRepository 을 찾아 사용했다.
http.csrf(csrf -> csrf
.csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse()) // CSRF 토큰을 쿠키로 관리
)
CookieCsrfTokenRepository 을 통해 CSRF 토큰을 쿠키로 클라이언트에게 전달하고, 브라우저 스크립트로도 읽을 수 있게 withHttpOnlyFalse() 속성을 주었다.
프론트에서는 서버가 전달한 CSRF 토큰을 포함한 쿠키 XSRF-TOKEN를 받을 수 있어야하며,
해당 CSRF 토큰을 HTTP 요청 헤더에 X-XSRF-TOKEN으로 추가해야한다.
const csrfToken = document.cookie
.split('; ')
.find(row => row.startsWith('XSRF-TOKEN='))
?.split('=')[1];
fetch('/api/endpoint', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-XSRF-TOKEN': csrfToken,
},
body: JSON.stringify({ key: 'value' }),
});
x-xsrf 와 csrf 는 동일!
?. 결론
JWT 를 사용하면 탈취 및 공격에 대비하기 용이하다~
코드는 나중에 리팩토링하고 올려야겠다.. 호호 호호..
액세스 리프레시도!
'백엔드 > Spring' 카테고리의 다른 글
Spring 포인트 컷? - AOP (0) | 2024.06.24 |
---|---|
Spring - 로깅? 요청 매핑? (0) | 2024.04.17 |
Spring과 웹 애플리케이션 (1) | 2023.10.29 |