JWT란 무엇일까?

coldrice99·2024년 10월 25일
0
post-thumbnail

🍪 JWT(Json Web Token)란?

JWT는 JSON 포맷을 이용하여 사용자에 대한 속성을 저장하는 Claim 기반의 Web Token으로, 토큰의 한 종류입니다. 일반적으로 쿠키 저장소를 사용하여 JWT를 저장합니다.

JWT 사용 이유

서버가 1대인 경우

Session1이 모든 클라이언트의 로그인 정보를 소유하고 있습니다.

서버가 2대 이상인 경우

서버의 대용량 트래픽 처리를 위해 서버 2대 이상 운영이 필요할 수 있습니다. 각 서버마다 다른 클라이언트 로그인 정보를 가지고 있을 수 있습니다.

  • Session1: Client1, Client2, Client3
  • Session2: Client4
  • Session3: Client5, Client6

만약 Client1의 로그인 정보를 가지고 있지 않은 Server2나 Server3에 API 요청을 하게 되면 문제가 발생할 수 있습니다.

해결 방법

  1. Sticky Session: 클라이언트마다 요청 서버를 고정합니다.
  2. 세션 저장소 생성: 모든 세션을 저장하는 세션 저장소를 생성하여 모든 서버에서 모든 클라이언트의 API 요청을 처리할 수 있도록 합니다.

JWT 사용

로그인 정보를 서버에 저장하지 않고, 클라이언트에 로그인 정보를 JWT로 암호화하여 저장합니다. 이를 통해 인증/인가를 수행합니다. 모든 서버에서 동일한 Secret Key를 소유하며, Secret Key를 통해 암호화 및 위조 검증(복호화 시)을 수행합니다.

JWT 장/단점

장점

  • 동시 접속자가 많을 때 서버 측 부하를 낮출 수 있습니다.
  • 클라이언트와 서버가 다른 도메인을 사용할 때 유용합니다. (예: 카카오 OAuth2 로그인 시 JWT Token 사용)

단점

  • 구현의 복잡도가 증가합니다.
  • JWT에 담는 내용이 커질수록 네트워크 비용이 증가합니다.
  • 생성된 JWT를 일부만 만료시킬 방법이 없습니다.
  • Secret Key가 유출되면 JWT가 조작될 수 있습니다.

JWT 사용 흐름

  1. 클라이언트가 username, password로 로그인 성공 시, 서버에서 "로그인 정보"를 JWT로 암호화합니다. (Secret Key 사용)
  2. 서버에서 직접 쿠키를 생성해 JWT를 담아 클라이언트 응답에 전달합니다.
  3. 브라우저 쿠키 저장소에 자동으로 JWT가 저장됩니다.

JWT 구조

JWT는 누구나 평문으로 복호화 가능합니다. 하지만 Secret Key가 없으면 JWT를 수정할 수 없습니다. 결국 JWT는 읽기 전용 데이터입니다.

  • Header: 암호화 방식 정보가 담겨 있습니다.
  • Payload: 사용자 정보가 담겨 있습니다.
  • Signature: Header와 Payload를 합쳐 Secret Key로 서명한 부분입니다.

예시:

{
  "alg": "HS256",
  "typ": "JWT"
}

{
  "sub": "1234567890",
  "username": "카즈하",
  "admin": true
}

HMACSHA256(
  base64UrlEncode(header) + "." +
  base64UrlEncode(payload),
  secret
)

Payload에 실제 유저의 정보가 들어있고, HEADER와 VERIFY SIGNATURE 부분은 암호화 관련된 정보 양식입니다.

JWT 다루기

JWT 관련 기능을 수행하기 위해 JwtUtil이라는 유틸리티 클래스를 구현했습니다.

JwtUtil 주요 기능

  1. JWT 생성
  2. JWT를 쿠키에 저장
  3. 쿠키에 들어있던 JWT 토큰을 서브스트링 처리
  4. JWT 검증
  5. JWT에서 사용자 정보 가져오기

토큰 생성에 필요한 데이터

  • AUTHORIZATION_HEADER: Header KEY 값
  • AUTHORIZATION_KEY: 사용자 권한 값의 KEY
  • BEARER_PREFIX: Token 식별자
  • TOKEN_TIME: 토큰 만료시간 (60분)

JWT 생성 예시

public String createToken(String username, UserRoleEnum role) {
    Date date = new Date();

    return BEARER_PREFIX +
            Jwts.builder()
                    .setSubject(username) // 사용자 식별자값(ID)
                    .claim(AUTHORIZATION_KEY, role) // 사용자 권한
                    .setExpiration(new Date(date.getTime() + TOKEN_TIME)) // 만료 시간
                    .setIssuedAt(date) // 발급일
                    .signWith(key, signatureAlgorithm) // 암호화 알고리즘
                    .compact();
}

JWT 쿠키에 저장

public void addJwtToCookie(String token, HttpServletResponse res) {
    try {
        token = URLEncoder.encode(token, "utf-8").replaceAll("\\+", "%20"); // Cookie Value 에는 공백이 불가능해서 encoding 진행

        Cookie cookie = new Cookie(AUTHORIZATION_HEADER, token); // Name-Value
        cookie.setPath("/");

        // Response 객체에 Cookie 추가
        res.addCookie(cookie);
    } catch (UnsupportedEncodingException e) {
        logger.error(e.getMessage());
    }
}

JWT 검증

public boolean validateToken(String token) {
    try {
        Jwts.parserBuilder().setSigningKey(key).build().parseClaimsJws(token);
        return true;
    } catch (SecurityException | MalformedJwtException | SignatureException e) {
        logger.error("Invalid JWT signature, 유효하지 않는 JWT 서명 입니다.");
    } catch (ExpiredJwtException e) {
        logger.error("Expired JWT token, 만료된 JWT token 입니다.");
    } catch (UnsupportedJwtException e) {
        logger.error("Unsupported JWT token, 지원되지 않는 JWT 토큰 입니다.");
    } catch (IllegalArgumentException e) {
        logger.error("JWT claims is empty, 잘못된 JWT 토큰 입니다.");
    }
    return false;
}

JWT에서 사용자 정보 가져오기

public Claims getUserInfoFromToken(String token) {
    return Jwts.parserBuilder().setSigningKey(key).build().parseClaimsJws(token).getBody();
}

마무리

이번 학습을 통해 JWT를 이용한 인증 방식에 대해 이해하고, 이를 구현하는 방법을 배울 수 있었습니다. JWT를 이용하면 서버 측 세션 관리를 하지 않고도 인증을 처리할 수 있어 서버의 부하를 줄이는 데 유용하다는 점이 인상적이었습니다.


https://github.com/coldrice99/spring-auth.git

profile
서두르지 않으나 쉬지 않고

0개의 댓글