개발/Java|Spring

JWT 토큰 라이브러리 java-jwt와 jjwt 간단 비교

달리초이 2023. 2. 9. 15:55

경험이 많지 않아 단언할 순 없지만, REST 아키텍처로 웹 서비스를 만들다 보면 대부분 토큰 인증 방식을 스프링 시큐리티에 접목하게 된다. 클라우드 서비스를 이용하는 추세를 보면, 세션 인증 방식보다 서버 분산 등 확장성에 더 좋은 토큰 인증 방식을 선호하게 된다. 서버를 여러대 두거나, 같은 사용자가 서로 다른 도메인 데이터를 요청할 경우 세션 유지를 위한 처리(비용)이 증가하기 때문이다. 그래서 토큰을 통해 사용자의 모든 요청을 확인하는 방식을 적용하는데, 이 때 JWT 토큰을 많이 사용한다.

 

JWT를 학습하다보면, JWT 라이브러리에도 다양한 선택지가 있다는 것을 알 수 있고, 그럼 이제 뭘 선택해야 할지 고민에 빠지게 된다. 이 글은 그 고민의 결과물이다.

 

 

java-jwt와 jjwt

Json Web Token을 다루는 라이브러리가 몇 가지 있는데, jwt.io 사이트에서 확인할 수 있다.

 

java 용으로 분류한 결과이다.

이 중에서 가장 유명한 JWT 라이브러리가 auth0에서 만든 Java-jwt와 okta에서 만든 jjwt 라이브러리이다.

둘 다 Json Web Token(JWT)을 인코딩하거나 디코딩하는 기능을 제공한다. 

jjwt가 상대적으로 더 많은 암호화 알고리즘을 제공하는 것을 확인할 수 있다.

 

기준일
2023-02-09
java-jwt jjwt
License
MIT license Apache-2.0 license
Stars 5.1k stars 8.9k stars
Forks 216 watching 268 watching
Watchers 879 forks 1.2k forks

 

Github Star 갯수나 Fork 갯수로도 jjwt가 앞서고 있다.

 

많이 찾아보진 않았지만, java-jwt 라이브러리는 사용하기 쉽다거나 가독성이 좋아서 편하다는 평이 있다. 반면, jjwt 라이브러리는 좀 더 고급 기능과 옵션을 제공한다고 한다. (깊게 사용해 본 적이 없어서 판단하기가 쉽진 않다.)
간단한 수준에서 사용을 해봤는데, 기능적으로 거의 유사하고 구현 방식만 약간씩 차이난다.

java-jwt 라이브러리가 Base64로 encoding 된 값을 decoding 할 수 있는 JWT.decode() 메소드를 지원하고 jjwt 라이브러리는 별도로 decode 메서드를 지원하지 않는다. Base64 decode는 손쉽게 할 수 있긴 하지만, 그럼에도 불구하고 주의(?) 차원에서 의도적으로 지원하지 않는 것 같다.

 

auth0의 java-jwt 라이브러리

JWT 생성 - JWT.create() 메서드 사용
JWT 검증 - JWT.require() 메서드 사용
JWT 클레임 추가 - withClaim() 메서드 사용 또는 with~~~() 메서드 사용

 

샘플 코드

package kr.pe.petaverse.petaverseapi.jwt;

import java.time.Instant;
import java.util.HashMap;
import java.util.Map;

import org.junit.jupiter.api.Test;
import org.springframework.util.Base64Utils;

import com.auth0.jwt.JWT;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.interfaces.DecodedJWT;

public class JavaJWTTest {

	String secretKey = "SecretKeyToGenJWTsSecretKeyToGenJWTsSecretKeyToGenJWTs";

	private void printToken(String token) {
		System.out.println("token: " + token);
		System.out.println("header: " + decodeToken(token.split("\\.")[0]));
		System.out.println("payload: " + decodeToken(token.split("\\.")[1]));
	}

	private String decodeToken(String token) {
		return new String(Base64Utils.decodeFromString(token));
	}

	@Test
	void test_auth0_token() {
		Map<String, Object> claims = new HashMap<>();
		claims.put("sub", "test");
		claims.put("name", "dalichoi");
		claims.put("admin", "true");
		claims.put("exp", Instant.now().plusSeconds(60*60*24).getEpochSecond());
		claims.put("iat", Instant.now().getEpochSecond());

		String auth0Token = JWT.create()
				// .withClaim("sub", "test")
				// .withClaim("name", "dalichoi")
				// .withClaim("admin", "true")
				// .withClaim("exp", Instant.now().plusSeconds(60*60*24).getEpochSecond())
				// .withClaim("iat", Instant.now().getEpochSecond())
				// .withExpiresAt(Date.from(Instant.now().plusSeconds(60*60*24)))	// exp 기본 제공
				// .withIssuedAt(Date.from(Instant.now()))												// iat 기본 제공
				.withPayload(claims)																						// map 데이터를 payload로 한 번에 넣을 수도 있다.
				.sign(Algorithm.HMAC256(secretKey));
		printToken(auth0Token);

		DecodedJWT verify = JWT.require(Algorithm.HMAC256(secretKey))
				.build()
				.verify(auth0Token);
		System.out.println("verify: " + verify.getClaims());
	}
}

 

 

 

okta의 jjwt 라이브러리

JWT 생성 - Jwts.builder() 메서드 사용
JWT 검증 - JWT.parserBuilder() 메서드 사용
JWT 클레임 추가 - addClaims() 메서드 사용 또는 set~~~() 메서드 사용

 

샘플 코드

package com.example.springsecuritystudy.jwt;

import java.time.Instant;
import java.util.HashMap;
import java.util.Map;

import org.junit.jupiter.api.Test;
import org.springframework.util.Base64Utils;

import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jws;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import io.jsonwebtoken.security.Keys;

class JwtUtilsTest {

	String secretKey = "SecretKeyToGenJWTsSecretKeyToGenJWTsSecretKeyToGenJWTs";

	private void printToken(String token) {
		System.out.println("token: " + token);
		System.out.println("header: " + decodeToken(token.split("\\.")[0]));
		System.out.println("payload: " + decodeToken(token.split("\\.")[1]));
	}

	private String decodeToken(String token) {
		return new String(Base64Utils.decodeFromString(token));
	}

	@Test
	void test_okta_token() {
		Map<String, Object> claims = new HashMap<>();
		claims.put("sub", "test");
		claims.put("name", "dalichoi");
		claims.put("admin", "true");
		claims.put("exp", Instant.now().plusSeconds(60*60*24).getEpochSecond());
		claims.put("iat", Instant.now().getEpochSecond());

		String oktaToken = Jwts.builder()
				// .setHeaderParam("typ", "JWT")
				// .setHeaderParam("alg", "HS256")
				// .setHeaderParam("kid", "key1")
				// .setSubject("test")
				// .setIssuedAt(java.util.Date.from(Instant.now()))
				// .setExpiration(java.util.Date.from(Instant.now().plusSeconds(60*60*24)))
				.addClaims(claims)
				.signWith(Keys.hmacShaKeyFor(secretKey.getBytes()), SignatureAlgorithm.HS256)
				.compact();
		printToken(oktaToken);

		Jws<Claims> claimsJws = Jwts.parserBuilder()
				.setSigningKey(Keys.hmacShaKeyFor(secretKey.getBytes()))
				.build()
				.parseClaimsJws(oktaToken);
		System.out.println("claimsJws: " + claimsJws);
	}

}

 

 

기능적으로 워낙 비슷하다보니 개발 환경이나 취향에 따라 적절히 선택하면 될 것 같다.

 

728x90
반응형