jwt를 작성하다보니 개념이 잘 이해가 안돼. 리프레시 토큰? 엑세스 토큰? 이런건 뭐하는 개념이야?
🔐 JWT (JSON Web Token)
JWT는 인증 정보를 담은 디지털 토큰이야. 클라이언트가 서버에 로그인 요청을 하면, 서버는 JWT를 만들어서 클라이언트에 전달하고, 클라이언트는 그걸 저장해서 이후 요청에 첨부해. 서버는 이 토큰을 보고 "이 사용자가 인증된 사용자구나"를 확인해.
🧾 Access Token (액세스 토큰)
- 사용 목적: 인증된 사용자가 API 요청을 할 때 자격 증명으로 사용.
- 특징:
- **수명(Time-to-Live)**이 짧아 (보통 몇 분 ~ 1시간).
- 탈취되더라도 위험이 제한됨 (짧은 시간만 유효하니까).
- 클라이언트는 서버에 요청할 때 이 토큰을 Authorization 헤더에 담아서 보냄.
Authorization: Bearer <ACCESS_TOKEN>
♻️ Refresh Token (리프레시 토큰)
- 사용 목적: Access Token이 만료됐을 때, 새로운 Access Token을 발급받기 위한 용도.
- 특징:
- 보통 수명이 깁니다 (며칠 ~ 몇 주).
- 서버 또는 안전한 저장소(예: HTTPOnly 쿠키)에 저장함.
- Refresh Token 자체로는 API 호출이 안 됨. 오직 Access Token 갱신용으로만 사용됨.
💡 왜 둘로 나눌까?
Access Token을 짧게 가져가고, Refresh Token을 따로 쓰는 이유는 보안 때문이야.
- Access Token만 쓰면 유출됐을 때 큰 피해를 볼 수 있음.
- Access Token을 짧게 하고, Refresh Token으로만 갱신하게 하면 유출 위험을 최소화할 수 있어.
- 예를 들어: Access Token이 탈취되면 금방 만료돼서 못 쓰고, Refresh Token은 안전한 곳에만 저장돼서 보호됨.
🔁 작동 흐름 요약
- 사용자가 로그인 → Access Token + Refresh Token 발급
- 사용자가 API 요청할 때 → Access Token 사용
- Access Token 만료 → Refresh Token으로 새 Access Token 요청
- Refresh Token도 만료 → 다시 로그인 필요
필요하면 그림으로도 정리해줄 수 있어!
혹시 JWT로 구현 중인 구조나 코드가 있다면 같이 봐줘도 설명이 더 명확해질 수 있어.
Q2.
그림으로도 그려주고,
func GenerateJWT(id, name string) (string, error) {
log.Println("[DEBUG] JWT 작성 시작")
jti := uuid.NewString() // 고유한 토큰 ID 생성
secret := uuid.NewString() // 랜덤한 서명키 생성
expiration := time.Hour * 1 // 1시간 유효
// Redis에 jti -> secret 저장
err := redisdb.RDB.Set(redisdb.Ctx, jti, secret, expiration).Err()
if err != nil {
return "", fmt.Errorf("redis 저장 실패: %v", err)
}
log.Println("[DEBUG] JWT 서명 작성 완료")
// jwt에 담을 사용자 정보와 메타데이터
claims := jwt.MapClaims{
"id": id,
"name": name,
"jti": jti,
"exp": time.Now().Add(expiration).Unix(),
}
// JWT 생성 HS256 알고리즘
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
log.Println("[DEBUG] JWT 작성 완료")
// 토큰을 비밀키로 서명해서 문자열 반환
return token.SignedString([]byte(secret))
}
// JWT 유효성 검증 함수
// JWT의 jti를 추출하고, redis에서 서명 키를 가져와 유효성 검사 수행
func ValidateJWT(tokenStr string) (jwt.MapClaims, error) {
log.Println("[DEBUG] JWT 검증 시작")
// jwt에서 먼저 jti 클레임만 파싱(서명 검증 없이 파싱)
parser := jwt.Parser{}
claims := jwt.MapClaims{}
_, _, err := parser.ParseUnverified(tokenStr, claims)
if err != nil {
return nil, fmt.Errorf("JWT 파싱 실패: %v", err)
}
// jti 클레임 추출
jti, ok := claims["jti"].(string)
if !ok {
return nil, fmt.Errorf("jti 클레임 누락됨")
}
log.Println("[DEBUG] jti 파싱 완료")
// Redis에서 jti에 해당하는 secret 조회
secret, err := redisdb.RDB.Get(redisdb.Ctx, jti).Result()
if err != nil {
return nil, fmt.Errorf("redis에서 Secret 조회 실패: %v", err)
}
log.Println("[DEBUG] redis에서 Secret 조회 완료")
// 실제 JWT 검증 (서명 포함)
token, err := jwt.Parse(tokenStr, func(token *jwt.Token) (interface{}, error) {
return []byte(secret), nil
})
if err != nil || !token.Valid {
return nil, fmt.Errorf("유효하지 않은 토큰")
}
// 클레임 최종 추출
finalClaims, ok := token.Claims.(jwt.MapClaims)
if !ok {
return nil, fmt.Errorf("클레임 파싱 실패")
}
log.Println("[DEBUG] JWT 검증 완료")
return finalClaims, nil
}
// HTTP 요청의 Authorization 헤더에서 JWT를 추출하고, 유효성 검사까지 수행
func AuthorizeJWT(ctx *fasthttp.RequestCtx) (jwt.MapClaims, error) {
// Authorization 헤더에서 jwt 추출
auth := string(ctx.Request.Header.Peek("Authorization"))
if !strings.HasPrefix(auth, "Bearer ") {
return nil, fmt.Errorf("'Authorization' 헤더 없음")
}
log.Println("[DEBUG] JWT 인증 시작")
// Berarer 접두사 제거
tokenStr := strings.TrimPrefix(auth, "Bearer ")
// JWT 유효성 검사
claims, err := ValidateJWT(tokenStr)
if err != nil {
return nil, fmt.Errorf("JWT 검증 실패: %v", err)
}
log.Println("[DEBUG] JWT 인증 성공")
return claims, nil
}
이 코드에서 사용하는 토큰은 어떤 토큰인거야?
A2.
아주 좋아! 지금 제공해준 코드를 보면, **GenerateJWT에서 생성하는 토큰은 "Access Token"**이야.
그리고 현재 구조상 "Refresh Token"은 따로 존재하지 않아.
✅ 지금 코드 구조에서 사용하는 토큰의 정체
🟢 Access Token (✔ 사용 중)
- GenerateJWT(id, name) 함수가 생성하는 JWT는 사용자의 인증 정보를 담고 있고,
- **1시간 유효한 만료 시간(exp)**을 가지고 있음
- JWT 안에 들어있는 jti 값을 기준으로 Redis에 서명키를 저장하고,
- 이후 검증 시 이 jti로 Redis에서 secret을 다시 조회해서 서명을 검증함
👉 즉, 이건 Access Token의 역할을 하는 거야.
🔴 Refresh Token (✖ 현재 없음)
- Refresh Token은 Access Token이 만료됐을 때 새 Access Token을 발급받기 위한 용도인데,
- 너의 코드에서는 그런 용도의 별도 토큰이 존재하지 않아.
👉 만약 Access Token이 만료되면 지금은 다시 로그인하거나 새로 발급받아야 해.
🔁 Access / Refresh Token 도식
너의 이해를 돕기 위해 아래에 Access Token / Refresh Token 구조와 흐름을 정리한 그림을 만들어봤어:
🔽 Access Token & Refresh Token 흐름
[로그인 요청]
↓
[서버에서 사용자 인증 성공]
↓
[Access Token + Refresh Token 발급]
↓
[Access Token → 클라이언트 저장 (ex. 메모리)]
[Refresh Token → 보안 저장소 (ex. HTTPOnly 쿠키)]
↓
[API 요청 시 Access Token 첨부]
↓
[Access Token 만료됨]
↓
[Refresh Token으로 새로운 Access Token 요청]
↓
[새로운 Access Token 발급]
✅ JWT 인증 흐름 (Access Token + Refresh Token)
1️⃣ 클라이언트 → 서버 : 로그인 요청
설명: 사용자가 아이디와 비밀번호 등으로 로그인 요청을 보냅니다.
2️⃣ 서버 → 클라이언트 : Access Token과 Refresh Token 발급
설명: 서버는 인증 정보를 확인한 뒤, Access Token과 Refresh Token을 생성하여 클라이언트에 전달합니다.
3️⃣ 클라이언트 → 서버 : API 요청 시 Access Token 포함
설명: 클라이언트는 이후 API 요청을 보낼 때 Access Token을 HTTP 헤더에 포함하여 보냅니다.
4️⃣ 서버 : Access Token의 유효성 검증
설명: 서버는 전달받은 Access Token을 검증하여 사용자의 권한을 확인합니다.
5️⃣ Access Token 만료 시 → 클라이언트 → 서버 : Refresh Token으로 재요청
설명: Access Token이 만료된 경우, 클라이언트는 보관 중인 Refresh Token을 이용하여 새 Access Token을 요청합니다.
6️⃣ 서버 : Refresh Token 검증 후 새로운 Access Token 발급
설명: 서버는 Refresh Token이 유효하다면, 새로운 Access Token을 생성하여 클라이언트에 전달합니다.
'프로그래밍 개념 정리' 카테고리의 다른 글
Gaia-X는 Data Space가 아니다 (0) | 2025.04.22 |
---|---|
EDC 핵심 기능 (0) | 2025.04.22 |
임시 JWT 비교 (0) | 2025.04.11 |
상수와 리터럴 차이 (0) | 2025.04.04 |
라이브러리와 패키지, 핸들러 (0) | 2025.04.04 |