본문 바로가기

Language/Spring

[JWT] JwtUtil 사용 예제 및 설명

728x90
Enum Class = UserRole
public enum UserRole {
    USER(Authority.USER),  // 사용자 권한
    ADMIN(Authority.ADMIN);  // 관리자 권한

    private final String authority;

    UserRole(String authority) {
        this.authority = authority;
    }

    public String getAuthority() {
        return this.authority;
    }

    public static class Authority {
        public static final String USER = "ROLE_USER";
        public static final String ADMIN = "ROLE_ADMIN";
    }
}
JwtUtil 멤버 변수 선언
@Component
public class JwtUtil {
    // Header KEY 값
    public static final String AUTHORIZATION_HEADER = "Authorization";
    // 사용자 권한 값의 KEY
    public static final String AUTHORIZATION_KEY = "auth";
    // Token 식별자
    public static final String BEARER_PREFIX = "Bearer ";
    // 토큰 만료시간(ms)
    private final long TOKEN_TIME = 30 * 60 * 1000L; // 30분
    // Base64 Encode 한 SecretKey
    @Value("${jwt.secret.key}")
    private String secretKey;
    private Key key;
    private final SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256;

    // 로그 설정
    public static final Logger logger = LoggerFactory.getLogger("JWT 관련 로그");
}
  • AUTHORIZATION_HEADER = "Authorization"
    • Cookie의 Name으로 설정할 값(Key값)

  • AUTHROZATION_KEY = "auth"
    • JWT의 Payload의 권한의 key값
{
	"auth" : "user"
}
  • BEARER_PREFIX = "Bearer "
    • JWT Token의 Value 앞에 붙는 접두사
    • 반드시 Bearer로 설정할 필요는 없으나, 'Bearer(스페이스)'로 사용하는 것이 일종의 약속

  • TOKEN_TIME = 30 * 60 * 1000L
    • JWT의 만료 시간(30분으로 설정)
    • 30 = 분 / 60 = 초 / 1000 = ms초
  • secretKey
    • (@Value = ${jwt.secret.key}) ⇒ application.yml 또는 properties에 선언한 환경 변수
    • JWT 서명(Signature)을 생성하고 검증할 때 사용되는 비밀 키(secret key)
  • signatureAlgorithm = SignatureAlgorithm.HS256
    • 암호화 알고리즘 선택
    • SignatureAlgorithm은 Enum으로 구현
public enum SignatureAlgorithm {
    NONE("none", "No digital signature or MAC performed", "None", (String)null, false, 0, 0),
    HS256("HS256", "HMAC using SHA-256", "HMAC", "HmacSHA256", true, 256, 256, "1.2.840.113549.2.9"),
    HS384("HS384", "HMAC using SHA-384", "HMAC", "HmacSHA384", true, 384, 384, "1.2.840.113549.2.10"),
    HS512("HS512", "HMAC using SHA-512", "HMAC", "HmacSHA512", true, 512, 512, "1.2.840.113549.2.11")
    // 생략
}

 

  • logger
    • 로깅을 위한 변수
    • 대신 Slf4j를 사용해도 됨
init 메서드
@PostConstruct
public void init() {
    byte[] bytes = Base64.getDecoder().decode(secretKey);
    key = Keys.hmacShaKeyFor(bytes);
}
  • @PostConstruct
    • 의존성 주입이 이루어진 뒤 초기화를 수행하는 메서드
    • 객체가 완전히 초기화된 후에 추가적인 설정이나 초기화 작업을 수행하기 위해 사용
    • Spring Context에 빈으로 등록 → 의존성 주입 → 자동으로 @PostConstruct가 붙은 메서드 실행

 

addJwtToCookie 메서드
public String createToken(String username, UserRole 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();
}
  • setSubject(String s)
    • 역할의 주체 설정
      → 보통 사용자의 식별자(userName, userId 등)
    • JWT에서의 의미 : "sub" 클레임에 해당
{
  "sub": "username"
}

 

  • claim(String name, Object value)
    • 토큰에 커스텀 클레임을 추가
    • JWT에서의 의미 : JWT payload의 key-value 쌍으로 토큰에 포함될 추가적인 정보를 정의
{
  "auth": "ROLE_USER"
}

 

  • setExpiration(Date expiration)
    • 토큰의 만료 시간 설정
    • JWT에서의 의미 : "exp" 클레임에 해당
      → 해당 시간을 지나면 유효하지 않은 토큰이 됨
{
  "exp": 1693412185
}

 

  • setIssuedAt(Date issuedAt)
    • 토큰이 발급된 시간 설정
    • JWT에서의 의미 : "iat" 클레임에 해당
      → 토큰이 언제 생성되었는지를 나타냄, 주로 만료 시간과 함께 사용
{
  "iat": 1693408585
}

 

  • signWith(Key key, SignatureAlgorithm algorithm)
    • 지정한 키와 알고리즘을 사용하여 JWT에 서명을 추가
      토큰의 위변조를 방지하는 데 사용
    • JWT에서의 의미 : 서명(Signature) 부분에 해당
      → JWT의 무결성을 보장
  • compact()
    • 설정된 모든 정보를 바탕으로 JWT를 생성하여 문자열 반환
    • JWT에서의 의미 :  앞서 설정한 헤더, 페이로드, 서명을 하나의 JWT 토큰으로 압축(Compact)하여 최종 결과를 반환

 

addJwtToCookie 메서드
  • Spring WebFlux의 ServerWebExchange를 사용하여 비동기식 웹 응답을 다루는 코드
public void addJwtToCookie(String token, ServerWebExchange exchange) {
    ResponseCookie cookie = ResponseCookie.from(AUTHORIZATION_HEADER, token)
            .path("/")
            .httpOnly(true)
            .build();
    exchange.getResponse().addCookie(cookie);
}
  • ResponseCookie.from(String name, String value)
    • 객체를 생성하는 빌더 메서드
    • key-value 형태로 '이름 : 값'을 지정

 

  • .path("/")
    • 쿠키의 경로 설정
    • 쿠키가 유효한 URL 패턴을 정의하며, 이 경로 이하의 모든 요청에서 쿠키가 전송
    • /는 애플리케이션의 모든 경로에서 쿠키를 사용할 수 있도록 설정
  • .httpOnly(true)
    • 쿠키의 HttpOnly 속성을 설정
    • 속성이 true일 경우 JavaScript와 같은 클라이언트 사이드 스크립트에서 쿠키에 접근 불가
    • XSS(교차 사이트 스크립팅) 공격으로부터 쿠키를 보호하는 데 도움
  • exchange.getResponse() 
    • 현재 HTTP 응답 객체를 가져옴 (HttpServletResponse와 비슷한 역할)
    • 이 객체를 사용하여 응답에 쿠키를 추가하거나 다른 작업을 수행 가능
  • .addCookie(ResponseCookie cookie) 
    • 응답 객체에 쿠키를 추가
    • 이 메서드는 ResponseCookie 객체를 받아서 응답에 포함
subStringToken 메서드
public String substringToken(String tokenValue) {
    if (StringUtils.hasText(tokenValue) && tokenValue.startsWith(BEARER_PREFIX)) {
        return tokenValue.substring(7);
    }
    logger.error("Not Found Token");
    throw new NullPointerException("Not Found Token");
}

 

 

  • StringUtils.hasText(String str)
    • str이 null이거나 공백이 아닌 지 확인
  • tokenValue.startWith(String prefix)
    • 토큰이 "Bearer "로 시작하는 지 확인
  • tokenValue.substring(7)
    • "Bearer "로 시작하기 때문에 떼고, 뒤의 토큰 내용만 반환
validateToken 메서드
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;
}
  • Jwts.parserBuilder()
    • JWT 파서를 생성하기 위한 빌더를 반환
    • JWT를 파싱할 때 사용할 다양한 설정을 구성 가능
  • setSigningKey(key)
    • JWT를 검증하는 데 사용할 서명 키를 설정
    • 이 키는 JWT가 서명될 때 사용된 것과 동일해야 하며, 이를 통해 JWT의 서명이 유효한지 확인 가능
    • key는 JWT 서명에 사용되는 비밀 키로 JwtUtil 클래스에서 @PostConstruct를 통해 초기화된 Key 객체
  • build()
    • build() 메서드는 설정된 빌더를 사용하여 JwtParser 객체를 생성
    • JwtParser는 실제 JWT를 파싱하고 검증하는 기능을 제공
  • parseClaimsJws(token)
    • parseClaimsJws(token)는 주어진 token을 파싱하여 JWT의 서명과 유효성을 검증
    • parseClaimsJws 메서드는 유효한 JWT를 포함하는 Jws<Claims> 객체를 반환
    • 이 객체는 JWT의 서명이 유효한지와 JWT의 페이로드(claims)를 포함
    • 이 메서드는 JWT의 서명올바르지 않거나 만료된 경우, 또는 다른 검증 오류가 발생한 경우 예외 throw
getUserInfoFromToken 메서드
public Claims getUserInfoFromToken(String token) {
    return Jwts.parserBuilder().setSigningKey(key).build().parseClaimsJws(token).getBody();
}
  • getBody()
    • JWT의 payload(클레임)를 반환하는 메서드
    • Jws<Claims> 객체에서 클레임(payload) 부분을 추출하여 반환
    • 반환되는 Claims 객체는 JWT의 클레임을 포함하며, 클레임은 JWT의 payload에 저장된 데이터
      • Ex) 사용자 정보, 권한, 만료 시간 등 다양한 정보를 담고 있을 수 있음
728x90