[백업][가리사니] jwt 라이브러리
java, jwt, kotlin

이 문서는 가리사니 개발자 포럼에 올렸던 글의 백업 파일입니다. 오래된 문서가 많아 현재 상황과 맞지 않을 수 있습니다.

자바, 코틀린 용 JWT 라이브러리를 만들어봤습니다.

  • https://github.com/saro-lab/jwt

QUICK START

gradle kts

implementation('me.saro:jwt:2.0.1')

gradle

compile 'me.saro:jwt:2.0.1'

maven

<dependency>
  <groupId>me.saro</groupId>
  <artifactId>jwt</artifactId>
  <version>2.0.1</version>
</dependency>

자바

단일키 예제

// 사용할 알고리즘 선언
var alg = new JwtEs256();
var key = alg.newRandomJwtKey();

// 클래임스 (payload의 객체) 선언
var claims = JwtClaims.create();
claims.issuedAt(OffsetDateTime.now());
claims.notBefore(OffsetDateTime.now().minusMinutes(1));
claims.expire(OffsetDateTime.now().plusMinutes(30));
claims.id("jti value");
claims.issuer("iss value");
claims.subject("sub value");
claims.audience("aud value");
claims.claim("custom", "custom value");

System.out.println(claims);

// jwt로 만듬 (위 newRandomJwtKey 를 가지고 만듬)
var jwt = alg.toJwt(key, claims);

System.out.println(jwt);

// jwt로부터 새로운 클래임스 생성
var newClaims = Assertions.assertDoesNotThrow(() -> alg.toJwtClaims(jwt, key));

System.out.println(newClaims);

// 비교
Assertions.assertEquals(newClaims.id(), "jti value");
Assertions.assertEquals(newClaims.issuer(), "iss value");
Assertions.assertEquals(newClaims.subject(), "sub value");
Assertions.assertEquals(newClaims.audience(), "aud value");
Assertions.assertEquals(newClaims.claim("custom"), "custom value");

동적키 예제 (header의 kid 사용)

// 사용할 알고리즘 선언
var alg = new JwtEs256();

// 키맵
var keyMap = new HashMap<String, JwtKey>();

// jwt 리스트
var jwtList = new ArrayList<String>();

// 랜덤한 키맵을 만든다.
for (int i = 0 ; i < 30 ; i++) {
    var kid = UUID.randomUUID().toString();
    var key = alg.newRandomJwtKey();
    keyMap.put(kid, key);

    // key는 db에 스트링형태로 저장해서 불러와서 파싱할 수 있다.
    // 이 원리로 여러서버가 공유 가능하다.
    // - key.stringify()
    // - alg.toJwtKey(key.stringify())
}

// 랜덤한 키를 가지고 jwt 를 만들어서 jwt 키 리스트에 쌓아둔다.
for (int i = 0 ; i < 10 ; i++) {
    var claims = JwtClaims.create();
    claims.issuedAt(OffsetDateTime.now());
    claims.notBefore(OffsetDateTime.now().minusMinutes(1));
    claims.expire(OffsetDateTime.now().plusMinutes(30));
    claims.id("jti value " + i);
    claims.issuer("iss value " + i);
    claims.subject("sub value " + i);
    claims.audience("aud value " + i);
    claims.claim("custom", "custom value " + i);

    var randomKid = (String)keyMap.keySet().toArray()[(int)(Math.random() * keyMap.size())];
    var randomKey = keyMap.get(randomKid);

    // make jwt with key / kid(header)
    var jwt = alg.toJwt(randomKey, claims, randomKid);
    jwtList.add(jwt);
}

// 디코드 및 검증
for (int i = 0 ; i < 10 ; i++) {
    var jwt = jwtList.get(i);
    var header = alg.toJwtHeader(jwt);
    var key = keyMap.get(header.getKid());
    var claims = alg.toJwtClaims(jwt, key);

    System.out.println();
    System.out.println("jwt : " + jwt);
    System.out.println(header);
    System.out.println(claims);

    Assertions.assertEquals(claims.id(), "jti value " + i);
    Assertions.assertEquals(claims.issuer(), "iss value " + i);
    Assertions.assertEquals(claims.subject(), "sub value " + i);
    Assertions.assertEquals(claims.audience(), "aud value " + i);
    Assertions.assertEquals(claims.claim("custom"), "custom value " + i);
}

코틀린

단일키 예제

// 사용할 알고리즘 선언
val alg = JwtEs256()
val key = alg.newRandomJwtKey()

// 클래임스 (payload의 객체) 선언
val claims = create()
claims.issuedAt(OffsetDateTime.now())
claims.notBefore(OffsetDateTime.now().minusMinutes(1))
claims.expire(OffsetDateTime.now().plusMinutes(30))
claims.id("jti value")
claims.issuer("iss value")
claims.subject("sub value")
claims.audience("aud value")
claims.claim("custom", "custom value")

println(claims)

// jwt로 만듬 (위 newRandomJwtKey 를 가지고 만듬)
val jwt = alg.toJwt(key, claims)

println(jwt)

// jwt로부터 새로운 클래임스 생성
val newClaims = Assertions.assertDoesNotThrow<JwtClaims> { alg.toJwtClaims(jwt, key) }

println(newClaims)

// 비교
Assertions.assertEquals(newClaims.id(), "jti value")
Assertions.assertEquals(newClaims.issuer(), "iss value")
Assertions.assertEquals(newClaims.subject(), "sub value")
Assertions.assertEquals(newClaims.audience(), "aud value")
Assertions.assertEquals(newClaims.claim("custom"), "custom value")

동적키 예제 (header의 kid 사용)

// 사용할 알고리즘 선언
val alg = JwtEs256()

// 키맵
val keyMap = HashMap<String?, JwtKey>()

// jwt 리스트
val jwtList = ArrayList<String>()

// 랜덤한 키맵을 만든다.
for (i in 0..29) {
    val kid = UUID.randomUUID().toString()
    val key = alg.newRandomJwtKey()
    keyMap[kid] = key

    // key는 db에 스트링형태로 저장해서 불러와서 파싱할 수 있다.
    // 이 원리로 여러서버가 공유 가능하다.
    // - key.stringify()
    // - alg.toJwtKey(key.stringify())
}

// 랜덤한 키를 가지고 jwt 를 만들어서 jwt 키 리스트에 쌓아둔다.
for (i in 0..9) {
    val claims = create()
    claims.issuedAt(OffsetDateTime.now())
    claims.notBefore(OffsetDateTime.now().minusMinutes(1))
    claims.expire(OffsetDateTime.now().plusMinutes(30))
    claims.id("jti value $i")
    claims.issuer("iss value $i")
    claims.subject("sub value $i")
    claims.audience("aud value $i")
    claims.claim("custom", "custom value $i")
    val randomKid = keyMap.keys.toTypedArray()[(Math.random() * keyMap.size).toInt()] as String
    val randomKey = keyMap[randomKid]

    // make jwt with key / kid(header)
    val jwt = alg.toJwt(randomKey!!, claims, randomKid)
    jwtList.add(jwt)
}

// 디코드 및 검증
for (i in 0..9) {
    val jwt = jwtList[i]
    val header = alg.toJwtHeader(jwt)
    val key = keyMap[header.kid]
    val claims = alg.toJwtClaims(jwt, key)

    println()
    println("jwt : $jwt")
    println(header)
    println(claims)

    Assertions.assertEquals(claims.id(), "jti value $i")
    Assertions.assertEquals(claims.issuer(), "iss value $i")
    Assertions.assertEquals(claims.subject(), "sub value $i")
    Assertions.assertEquals(claims.audience(), "aud value $i")
    Assertions.assertEquals(claims.claim("custom"), "custom value $i")
}