JWT 토큰


JWT

Json Web Token, Json 기반의 컴팩트한 토큰 방식으로 클라이언트와 서버 간의 안전하게 정보를 교환할 수 있도록 설계된 것이다. 주로 사용자 인증과 권한 부여에 사용되며, 한 번 로그인 후 클라이언트는 발급된 토큰을 서버에 전달하여 추가 인증 없이 리소스에 접근할 수 있다.

왜 등장했나?

여러 대 서버 운영시 세션마다 다른 로그인 정보 소유한 경우를 가정한다. 여기서 문제는 Client 1의 로그인 정보를 가지고 있지 않은 Sever2 나 Server3 에 API 요청 시 발생한 경우이다.

케이스1

이럴 경우 클라이언트마다 요청 서버 고정하거나, 따로 세션 저장소 생성하여 모든 세션을 저장하는 방법이 있다. 그러나, 위 방법들은 설정해주는 과정이 귀찮은 방법들이다. 그래서 나온게 JWT 방법이다.

JWT1

로그인 정보를 서버에 저장하지 않고 클라이언트에 JWT로 암호화하여 저장하며 이 때, 서버는 동일한 시크릿 키를 소유해야 하고 이는 암호화 및 위조 검증에 사용이 된다.

사용 흐름

  1. 로그인: 클라이언트가 사용자 자격 증명을 서버에 전송.
  2. 토큰 발급: 서버는 자격 증명을 검증한 후 JWT를 생성하여 클라이언트에 전달.
  3. 토큰 저장: 클라이언트는 JWT를 로컬 스토리지, 쿠키 등 안전한 위치에 저장.
  4. 인증 요청: 클라이언트는 보호된 리소스에 접근할 때 JWT를 HTTP 헤더에 포함하여 요청.
  5. 토큰 검증: 서버는 요청받은 JWT의 서명을 검증하고, 유효한 경우 해당 요청을 처리.

사용법

아래는 JWT 사용 코드이다. 해당 코드로 JWT를 생성하고 이를 이용하여 은증하고 확인할 수 있다.

/**
 * Create JWT
 *
 * @param username
 * @param role
 * @return
 */
public String createJwt(String username, UserRole role) {
    Date date = new Date();
    Date expirationDate = new Date(date.getTime() + expirationTime);

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

/**
 * get JWT from header
 *
 * @param request
 * @return
 */
public String getJwtFromHeader(HttpServletRequest request) {
    String bearerToken = request.getHeader(AUTHORIZATION_HEADER);
    if (StringUtils.hasText(bearerToken) && bearerToken.startsWith(BEARER_PREFIX)) {
        return bearerToken.substring(BEARER_PREFIX.length());
    }
    return null;
}

/**
 * Valicate JWT
 *
 * @param token
 * @return
 */
public boolean validateToken(String token) {
    if (isTokenBlacklisted(token)) {
        log.error("JWT token is blacklisted");
        return false;
    }

    try {
        Jwts.parserBuilder().setSigningKey(key).build().parseClaimsJws(token);
        return true;
    } catch (SecurityException | MalformedJwtException | SignatureException e) {
        log.error("Invalid JWT signature");
    } catch (ExpiredJwtException e) {
        log.error("Expired JWT token");
    } catch (UnsupportedJwtException e) {
        log.error("Unsupported JWT token");
    } catch (IllegalArgumentException e) {
        log.error("JWT claims is empty");
    }
    return false;
}

/**
 * get User Info from JWT
 *
 * @param token
 * @return
 */
public Claims getUserInfoFromToken(String token) {
    return Jwts.parserBuilder().setSigningKey(key).build().parseClaimsJws(token).getBody();
}

이와 같이 JWT를 생성하고 검증하는 로직을 작성한 후, Spring Security 필터 체인에서 인증 및 권한 부여에 활용할 수 있다.