본문 바로가기
학습/Java

JJWT

코동이 2021. 3. 3.

Java JWT: JSON Web Token for Java and Android


JAVA JWT : JSON Web Token for Java and Android

 

JJWT는 JWT를 생성하고 증명하는 라이브러리로서 자바와 JVM과 안드로이드에서 가장 쉽게 사용하고 이해할 수 있는 것을 목표로 한다.

 

JJWT는  Apache 2.0 라이선스에 따라 JWT, JWS, JWE, JWK, JWA RFC 명세 및 오픈소스를 독점적으로 기반하는 순수 Java 구현이다.

 

라이브러리는 Okta' 선임 설계자인, Les Hazlewood에 의해 만들어졌으며, 컨트리뷰터들의 커뮤니티에서 지원하고 유지관리한다.

 

Okta는 개발자를 위한 완벽한 인증과 사용자 관리 API이다.

 

 

* 특징


- 모든 JDK와 Android에서 기능을 갖추고 있음

- 자동 보안 모범 사례이자 단언

- API를 배우고 읽는 것이 쉬움

- 쉽고 읽기 쉬운 fluent interfaces, IDE 자동완성으로 빠르게 코드 작성 가능

- 구현 된 모든 기능에 대해 완전히 RFC 사양을 준수하고 RFC 지정 테스트 벡터에 대해 테스트 됨

- 100 % 테스트 코드 적용 범위로 안정적인 구현. 말 그대로 전체 코드베이스의 모든 단일 메서드, 선언문 및 조건부 분기 변형이 테스트되고 모든 빌드를 통과해야함

- 모든 표준 JWS 알고리즘으로 디지털로 서명된 압축 JWTS(JWSs로도 알려진)를 생성하고 파싱하고 증명함

  • HS256: HMAC using SHA-256
  • HS384: HMAC using SHA-384
  • HS512: HMAC using SHA-512
  • ES256: ECDSA using P-256 and SHA-256
  • ES384: ECDSA using P-384 and SHA-384
  • ES512: ECDSA using P-521 and SHA-512
  • RS256: RSASSA-PKCS-v1_5 using SHA-256
  • RS384: RSASSA-PKCS-v1_5 using SHA-384
  • RS512: RSASSA-PKCS-v1_5 using SHA-512
  • PS256: RSASSA-PSS using SHA-256 and MGF1 with SHA-2561
  • PS384: RSASSA-PSS using SHA-384 and MGF1 with SHA-3841
  • PS512: RSASSA-PSS using SHA-512 and MGF1 with SHA-5121

- 다음과 같은 특징으로 편리함이 증가함

  • JWEs 뿐만 아니라, 어떠한 큰 JWT라도 Body 압축
  • Claims 단언 ( 특정 값 필요 )
  • 호환되는 JSON 파서( 예: Jackson ) 사용 시, POJO 마샬링 및 마샬링 해제 요청
  • 필요한 JWA 알고리즘에 기반한 안전한 Key 변환

현재 지원하지 않는 것

압축되지 않은 직렬화와 파싱

JWE ( JWT 암호화 )

-> 차후 버젼에서 이 특징들이 구현될 것!

 

 

Json Web Token이란 무엇인가?


JWT는 압축된, 증명 가능한 형태로 두 영역 사이에서 정보를 교환하는 수단이다.

 

JWT의 몸통에서 인코딩 된 정보의 비트를 claims라고 부른다. JWT의 확장된 형태는 JSON 형식에 있고, 그래서 각각의 claim은 JSON 객체에서 키 역할을 한다.

 

JWT는 JWS를 만들어서 암호로 서명을 할 수도 있고, JWE를 만들어서 암호화할 수 있다.

 

이것은 JWTs 사용자들에게 강력한 보안의 계층을 제공한다. 수신자는 서명을 확인하여 JWT가 변조되지 않았음을 높은 수준으로 확신한다.

 

사인된 JWT의 압축표현은 문자열인데, .으로 구분되어 3가지 파트로 나누어진다.

eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJKb2UifQ.ipevRNuRP6HflG8cFKnmUPtypruRC4fb1DWtoLL62SY

각각의 파트는 Base64URL-인코딩방식이다. 첫째는 header인데, 최소한 JWT를 서명하기 위해 사용되는 알고리즘을 명시해야 한다. 두번째는 body이다. JWT의 모든 claims가 인코딩 되어있다. 마지막은 signature이다. header에 명시된 알고리즘을 통해 header와 body의 조합을 전달하여 계산된다. 

 

처음 2개의 파트를 base 64 url 디코더를 통해 전달하면, 다음을 얻을 수 있다.

 

header

{
  "alg": "HS256"
}

body

{
  "sub": "Joe"
}

이 경우, 우리가 가지고 있는 정보는 SHA-256 알고리즘을 사용한 HMAC가 JWT를 서명하기 위해 사용되었다는 것이다.

body는 하나의 claim, Joe값을 가지는 sub를 가지고 있다.

 

구현에서 Registered Claims라고 불리는 표준 claims들이 많이 존재하는데, sub는 그중에 하나이다.

 

4.1.2 "sub" ( Subject ) Claim

The "sub" (subject) claim identifies the principal that is the subject of the JWT. The claims in a JWT are normally statements about the subject. The subject value MUST either be scoped to be locally unique in the context of the issuer or be globally unique. The processing of this claim is generally application specific. The "sub" value is a case-sensitive string containing a StringOrURI value. Use of this claim is OPTIONAL.

signature를 계산하기 위해서, 서명하기 위한 비밀키가 필요하다. key와 알고리즘에 대해서도 살펴 볼 것이다.

 

 

간단한 예제


대부분의 복잡한 것들은 쉽고 일기 쉬운 빌더 기반 fulent interface 뒤에 숨겨져 있으며, 빠른 코드 작성을 위해서 IDE 자동완성이 제공된다. 예를 들면 아래와 같다.

import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import io.jsonwebtoken.security.Keys;
import java.security.Key;

// 서명된 키가 필요하지만, 이 예제로 간단하게 생성할 수 있다.
// 키는 어플리케이션 설정에서 대신 해석될 것이다.
Key key = Keys.secretKeyFor(SignatureAlgorithm.HS256);

String jws = Jwts.builder().setSubject("Joe").signWith(key).compact();

너무 쉽다!

 

이 예제의 경우, 우리는

1. registered claim인 sub ( subject ) 에 Joe 를 선언하는 JWT를 빌드하며 (building)

2. HMAC-SHA-256 알고리즘에 적잡한 키를 이용하여 JWT를 서명하며 (signing)

3. 마지막으로 String 형태로 압축한다. (compacting)

 

서명된 JWT는 'JWS'라고 불린다. 이 jws의 String 결과는 다음과 같다.

eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJKb2UifQ.1KP0SsvENi7Uz1oQc07aXTL7kpQG5jBNIybqr60AlD4

이제 JWT를 증명할 차례이다(기대한 서명과 일치하지 않은 JWTs를 꼭 폐기할 것)

assert Jwts.parserBuilder().setSigningKey(key).build().parseClaimsJws(jws).getBody().getSubject().equals("Joe");

확인 : parseClaimsJws 메서드를 호출하는 것을 꼭 확인하라 (왜냐하면 비슷하게 생긴 다양한 메서드가 있기 때문이다)

만약 JWT에 잘못된 메서드를 파싱하면 UnsupportedJwtException이 발생한다.

 

여기서 확인할 수 있는 2가지가 있다. 이전에 key는 JWT 서명을 유효하게 하기위해 사용된다. 만약 JWT를 증명하는 것이 실패한다면, SignatureException ( JwtException으로 부터 상속하는 ) 이 발생한다. JWT가 유효하다면, 우리는 claims를 파싱하고 subject가 Joe가 설정되었다는 것을 증명한다.

 

만약, 파싱이나 서명의 유효성이 실패한다면? JwtException을 catch하고 다음처럼 처리해야 한다.

try {

    Jwts.parserBuilder().setSigningKey(key).build().parseClaimsJws(compactJws);

    //OK, we can trust this JWT

} catch (JwtException e) {

    //don't trust the JWT!
}

 

서명된 JWTs


JWT 명세는 JWT에 암호화 방식으로 서명하는 기능을 제공한다.

 

1. JWT는 우리가 알고 있는 사람에 의해서 생성되었다는 것을 보증한다. (인증)

2. JWT이 생성된 이후에, 어떠한 사람도 조작하거나 변경하지 않았음을 보증한다. (무결성)

 

인증과 무결성은 JWT가 우리가 신뢰할 수 있는 정보를 포함한다고 증명한다. 만약 JWT가 인증과 무결성 검사를 실패한다면, 우리는 항상 JWT를 거부해야하는데, 그것을 신뢰할 수 없기 때문이다.

 

그래서, 어떻게 JWT가 서명이 되는 것일까? 읽기 쉬운 수도코드로 살펴보자.

 

1. 우리가 JSON header와 body ( 'Claims'라고 알려진 ) 를 가지고 있다고 가정하자

 

header

{
  "alg": "HS256"
}

 

body

{
  "sub": "Joe"
}

 

2. JSON에서 모든 불필요한 공백들을 제거하자

String header = '{"alg":"HS256"}'
String claims = '{"sub":"Joe"}'

 

3. UTF-8 bytes와 Base64URL-인코딩을 각각 가져오자

String encodedHeader = base64URLEncode( header.getBytes("UTF-8") )
String encodedClaims = base64URLEncode( claims.getBytes("UTF-8") )

 

4. 인코딩 된 header와 claims를 연속적인 문자열 형태로 사슬처럼 잇자

String concatenated = encodedHeader + '.' + encodedClaims

 

5. 암호화 알고리즘과 충분히 강력한 암호화 비밀 혹은 개인 키를 사용하라

(여기서는 HMAC-SHA-256을 사용한다), 그리고 위에서 만든 문자열과 서명하라

Key key = getMySecretKey()
byte[] signature = hmacSha256( concatenated, key )

 

6. 서명들은 항상 byte 배열이기 때문에, signature를 Base64URL-인코딩으로 하고, 문자열에 '.' 뒤에 붙여라

String jws = concatenated + '.' + base64URLEncode( signature )

 

그러면 최종적으로 jws 문자열을 얻을 수 있다.

eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJKb2UifQ.1KP0SsvENi7Uz1oQc07aXTL7kpQG5jBNIybqr60AlD4

 

이것은 'JWS'라고 불린다 - signed JWT의 줄임말

 

물론, 아무도 이 모든 과정을 코드로 치기 원하지 않는다, 최악은, 만약 오타를 내면, 보안 문제와 취약성을 유발할 수 있다. 결론적으로, JJWT는 이 모든 것들을 관리하기 위해서 만들어졌다: JJWT는 완벽하게 JWSs의 생성과 JWSs의 파싱 및 증명을 자동으로 해준다.

 

JJWT를 이용해 어떻게 JWS를 생성하는지 확인하기 전에, 짧게나마  JWT 명세에 관련있는 서명 알고리즘과 키들에 대해 토론한다. 그것들을 이해하는 것은 적절한 JWS를 생성하기 위해서 중요하다.

 

** 서명 알고리즘 키

 

JWT 명세에는 12가지 표준 서명 알고리즘이 있으며 그것은 3개의 비밀 키 알고리즘, 9개의 비대칭 키 알고리즘이다.

  • HS256: HMAC using SHA-256
  • HS384: HMAC using SHA-384
  • HS512: HMAC using SHA-512
  • ES256: ECDSA using P-256 and SHA-256
  • ES384: ECDSA using P-384 and SHA-384
  • ES512: ECDSA using P-521 and SHA-512
  • RS256: RSASSA-PKCS-v1_5 using SHA-256
  • RS384: RSASSA-PKCS-v1_5 using SHA-384
  • RS512: RSASSA-PKCS-v1_5 using SHA-512
  • PS256: RSASSA-PSS using SHA-256 and MGF1 with SHA-256
  • PS384: RSASSA-PSS using SHA-384 and MGF1 with SHA-384
  • PS512: RSASSA-PSS using SHA-512 and MGF1 with SHA-512

이 모든 것이 io.jsonwebtoken.SignatureAlgorithm enum에 보여진다.

 

보안의 특정 관점에서 이 알고리즘들에 관해 가장 중요한 것은 JWT 명세 RFC 7518, Sections 3.2~3.5가 선택된 알고리즘에 대해 충분히 강력한 key를 사용해야만 한다고 요구하는 것이다.

 

이것은 JJWT ( 호완되는 라이브러리 ) 또한 사용자가 선택한 알고리즘을 위해서 충분히 강력한 키를 사용을 강제해야함을 의미한다. 만약 주어진 알고리즘에 대해 취약한 키를 사용한다면, JJWT는 거부하고 예외를 발생할 것이다.

 

이것은 사용자를 어렵게하려고 하는것이 아니라 약속이기 때문이다. JWT 명세와 그에따른 JJWT가 키 길이를 강조하는 이유는 만약 알고리즘의 필수적인 키 속성에 따르지 않는다면 특벼한 알고리즘의 보안 모델이 깨져버리기 떄문이다, 전혀 보안적인 요소가 없게 된다. 아무도 JWTs가 취약하길 원하지 않을 것이다.

 

그래서 요구사항은 무엇인가?

 

HMAC-SHA

 

JWT HMAC-SHA 서명 알고리즘 HS256, HS384, HS512는 비밀키가 최소한 알고리즘의 서명 길이만큼의 비트를 가지고 있어야함을 요구한다. RFC 7512 Section 3.2

  • HS256 은 HMAC-SHA-256이고, 256bits(32bytes)인 digest를 생성한다, 따라서 HS256은 사용자가 비밀키로 최소한 32bytes 길이를 사용하도록 요구한다.

 

  • HS384 은 HMAC-SHA-384이고, 384bits(48bytes)인 digest를 생성한다, 따라서 HS384은 사용자가 비밀키로 최소한 48bytes 길이를 사용하도록 요구한다.

 

  • HS512 은 HMAC-SHA-512이고, 512bits(64bytes)인 digest를 생성한다, 따라서 HS512은 사용자가 비밀키로 최소한 64bytes 길이를 사용하도록 요구한다.

RSA

Elliptic Curve

 

안전한 키 생성하기

 

만약에 사용자가 비트 길이 요구사항을 신경쓰기 싫다면, 혹은 단순히 더 쉽게 사용하길 원한다면, JJWT은 사용자가 쓰고 싶은 어떠한 JWT 서명 알고리즘에도 충분히 강력한 키를 만들 수 있는 io.jsonwebtoken.security.Keys 유틸 클래스를 제공한다.

 

비밀 키

 

만약에, JWT HMAC-SHA 알고리즘을 사용하기 위해서 충분히 강력한 SecretKey를 생성하기 원한다면, Keys.secretKeyFor(SignatureAlgorithm) 메서드를 사용하라.

 

SecretKey key = Keys.secretKeyFor(SignatureAlgorithm.HS256); //or HS384 or HS512

JJWT는 JCA 제공자의 KeyGenerator를 사용해서 주어진 알고리즘에 대해 일치하는 최소한의 길이로 안전한 랜덤 키를 생성한다.

 

만약에 사용자가 이 새로운 SecretKey를 저장하길 원한다면, Base64 (혹은 BaSE64URL) 인코딩을 사용할 수 있다.

String secretString = Encoders.BASE64.encode(key.getEncoded());

결과값인 secretString을 안전한 곳에 보관하도록 꼭 확인하라 - Base64-인코딩은 암호화가 아니다.

그래서, 아직도 이것은 민감한 정보이다. 사용자는 예를 들어 disk에 저장하기 이전에 더 암호화 할 수 있다.

 

비대칭 키

 

 

JWS 생성하기


JWS를 다음과 같은 방식으로 생성할 수 있다.

1. JwtBuilder 객체를 생성하기 위해 Jwts.builder() 메서드를 사용하라

2. 필요한 header 매개변수와 claims를 추가하기 위해 JwtBuilder 메서드를 호출하라

3. JWT를 서명하기 위해서 SecretKey 혹은 비대칭 PrivateKey를 지정하라

4. 압축하고 서명하기 위해서 compact() 메서드를 호출하라

 

예를 들어,

String jws = Jwts.builder() // (1)

    .setSubject("Bob")      // (2) 

    .signWith(key)          // (3)
     
    .compact();             // (4)

 

Header Parameters

JWT Header는 내용, 형식, 그리고 JWT의 Claims에 관련한 암호화 연산들에 대한 설정들을 제공한다.

 

 만약 하나 혹은 그 이상의 JWT header 매개변수들이 필요하다면, ( kid (KEY ID) 와 같은 header 매개변수 ), 사용자는 단지 JwtBuilder, setHeaderParm을 한번 혹은 여러번 호출하면 된다.

String jws = Jwts.builder()

    .setHeaderParam("kid", "myKeyId")
    
    // ... etc ...

setHeaderParam이 호출될 때마다, 그것은 Header 객체 내부에 key-value 쌍을 추가한다, 이미 존재하는 동일한 쌍에 대해서 덮어쓰기 한다.

 

주의 : alg 혹은 zip 같은 header 매개변수를 지정할 필요가 없는데, JJWT는 서명 알고리즘이나 압축 알고리즘에 따라서 자동적으로 설정하기 때문이다.

 

Header Instance

 만약에 사용자가 전체 header를 한번에 명세하길 원한다면, 사용자는 Jwts.header() 메서드를 사용해서 매개변수와 함께 빌드 할 수 있다. 

Header header = Jwts.header();

populate(header); //implement me

String jws = Jwts.builder()

    .setHeader(header)
    
    // ... etc ...

 

주의 : setHeader를 호출하는 것은 이미 설정 되어있을 수 있는 동일한 이름으로 기존 header 쌍들을 덮어쓸 것이다. 그와중에, JJWT는 명세된 header 객체에 어떠한 설정 여부에 관계없이 algzip을 덮어쓰기 할 것이다.

 

Header Map

만약 사용자가 한번에 전체 header를 설정하기 원한다면, Jwts.header()를 사용할 필요가 없다. JwtBuilder setHeader(Map) 메서드를 대신 사용하여라

Map<String,Object> header = getMyHeaderMap(); //implement me

String jws = Jwts.builder()

    .setHeader(header)
    
    // ... etc ...

주의 : setHeader를 호출하는 것은 이미 설정되어있을 수 있는 동일한 이름으로 기존 header 쌍들을 덮어쓸 수 있다.

그러나, JJWT는 명세된 header 객체에 어떠한 설정 여부에 관계없이 alg와 zip을 덮어쓰기 할 것이다.

 

Claims

Claims들은 JWT의 'body' 이고 JWT 생성자가 JWT 수신자가 확인하기를 바라는 정보들을 담고 있다.

 

Standard Claims

JwtBuilder는 JWT 명세에 정의된 표준 registered Claims 이름을 위해서 편리한 setter 메서드를 제공한다.

예를 들어,

String jws = Jwts.builder()

    .setIssuer("me")
    .setSubject("Bob")
    .setAudience("you")
    .setExpiration(expiration) //a java.util.Date
    .setNotBefore(notBefore) //a java.util.Date 
    .setIssuedAt(new Date()) // for example, now
    .setId(UUID.randomUUID()) //just an example id
    
    /// ... etc ...

 

Custom Claims

만약에 위에 있는 claims 표준 setter 메서드에 없는 커스텀 claims를 설정하는 것이 필요하다면, 사용자는 단지 JwtBuilder claim을 하나 혹은 여러번 정의하면 된다.

String jws = Jwts.builder()

    .claim("hello", "world")
    
    // ... etc ...

claim이 호출될 때마다, 내부의 Claims 객체에 단순하게 key-value 쌍을 더하게 된다, 잠재적으로는 존재하는 동일한 이름의 key/value 쌍을 덮어쓰게 된다.

 

 명백하게, 사용자는 어떠한 표준 claim 이름을 위해서 claim을 호출할 필요가 없다. 대신에, 가독성 향상을 위해서 각각의 표준 setter 메서드를 호출하는 것이 추천된다.

 

Claims Instance

 만약에 사용자가 모든 claims를 한번에 정의하기 원한다면, 사용자는 Jwts.claims() 메서드를 사용할 수 있고, claims를 빌드 할 수 있다.

Claims claims = Jwts.claims();

populate(claims); //implement me

String jws = Jwts.builder()

    .setClaims(claims)
    
    // ... etc ...

주의 : setClaims를 호출하는 것은 이미 설정 되어있을 수 있는 동일한 이름으로 기존 claim 쌍들을 덮어쓸 수 있다. 

 

Claims Map

만약 사용자가 한번에 전체 header를 설정하기 원한다면, Jwts.claims()를 사용할 필요가 없다. JwtBuilder setClaims(Map) 메서드를 대신 사용하면 된다.

Map<String,Object> claims = getMyClaimsMap(); //implement me

String jws = Jwts.builder()

    .setClaims(claims)
    
    // ... etc ...

주의 : setClaims를 호출하는 것은 이미 설정되어있을 수 있는 동일한 이름으로 기존 claim 쌍들을 덮어쓸 수 있다. 

 

Signing Key

JwtBuildersighWith 메서드를 호출함으로 서명 키를 명세하는 것이 추천된다. 그리고 JJWT가 명세화된 키에 대하여 가장 안전한 알고리즘을 결정하도록 하여라

String jws = Jwts.builder()

   // ... etc ...
   
   .signWith(key) // <---
   
   .compact();

예를 들어, 만약에 사용자가 256bits(32byets)의 SecretKeysignWith를 호출한다면, 더이상 HS384HS512는 강력하지 않다, 그래서 JJWT는 HS256을 사용해서 JWT를 자동으로 서명할 것이다.

 

signWith를 사용할 때, JJWT는 관련된 알고리즘 식별자를 통해 요구된 alg header를 자동으로 설정할 것이다. 

 

비슷하게, 만약에 사용자는 4096 비트의 길이를 가진 RSA PrivateKeysignWith를 호출하면, JJWT는 RS512 알고리즘을 사용할 것이고 자동으로 alg header에 RS512를 설정 할 것이다.

 

이런 선택 논리는 Elliptic Curve PrivateKey들에게도 적용된다.

 

확인 : 사용자는 JWTs를 PublicKey로 서명할 수 없다, 왜냐하면 이것은 항상 안전하지 않기 때문이다. JJWT는 어떠한 명세된 PublicKey가 InvalidKeyException을 서명하는 것을 거부 할 것이다.

 

SecretKey Formats

만약에 사용자가 HMAC-SHA 알고리즘으로 JWS를 서명하기 원하고, 사용자가 String 형식 혹은 인코딩 된 byte 배열 형태의 비밀 키를 가지고 있다면, 사용자는 그것을 signWith 메서드 매개변수로서 사용하기 위해 ScreteKey 객체로 변환해야 할 것이다.

 

만약에 사용자의 비밀 키가

SecretKey key = Keys.hmacShaKeyFor(encodedKeyBytes);

 

  • Base64-인코딩 string 이라면:
SecretKey key = Keys.hmacShaKeyFor(Decoders.BASE64.decode(secretString));

 

  • Base64URL-인코딩 string 이라면:
SecretKey key = Keys.hmacShaKeyFor(Decoders.BASE64URL.decode(secretString));

 

  • 인코딩 되지 않은 string ( 예 : 비밀번호 String )이라면:
SecretKey key = Keys.hmacShaKeyFor(secretString.getBytes(StandardCharsets.UTF_8));

secretString.getByts()를 호출하는 것은 항상 올바르지 않다. ( charset을 제공하지 않고 )

 

그러나, 인코딩되지 않은 비밀번호 string은 ( 예 : correcthorsebatterystaple ) 가능한 피해야 한다. 왜냐하면 그것들은 필연적으로 취약하고 의심스러운 키이기 때문이다. 안전한-무작위의 키들은 항상 대부분 강력하다. 가능하다면, 새로운 안전한-무작위 비밀 키를 대신에 생성하도록 해야한다.

 

SignatureAlgorithm Override

 몇몇 특정 명세에서, 주어진 키에 대해 JJWT의 기본으로 선택된 알고리즘을 덮어쓰고 싶을 수도 있다.

예를 들어, 사용자가 2048 비트의 RSA PrivateKey를 가지고 있다면, JJWT 자동적으로 RS256 알고리즘을 선택할 것이다. 만약에 사용자가 RS384 혹은 RS512를 대신에 사용하기를 원한다면, 사용자는 부가적인 매개변수로서 SignatureAlgorithm을 받아들이는 오버로드되어진 signWith 메서드를 사용할 수 있다.

   .signWith(privateKey, SignatureAlgorithm.RS512) // <---
   
   .compact();

 이것이 허용되는 이유는 JWT 명세가 RSA 알고리즘에서 어떠한 RSA key가 2048 비트 이상을 가지도록 허용하기 때문이다. JJWT는 key로 4096 비트 이상을 가지는 RS512 를 가장 선호한다, 그 다음은 3072비트 이상을 지닌 RS384이고 마지막으로는 2048 비트 이상을 지닌 RS256이다.

 

그러나 모든 경우에, 선택된 알고리즘과 상관 없이, JJWT는 명세된 키가 JWT 사양 요구에 따라 알고리즘에 사용 될 수 있다고 주장할 것이다.

 

JWS Compression

 만약에 사용자의 JWT claims 설정이 크다면, JJWT가 자신의 JWS를 읽고 파싱하는 동일한 라이브러리라고 확신한다면, 사용자는 사이즈를 줄이기 위해 JWS를 압축하고 싶을지도 모른다. 이것이 JWS의 표준 특징이 아님을 기억하라, 다른 JWT 라이브러리에서도 마찬가지로 지원하지 않을 것이다.

 

주요 압축 부분을 통해서 어떻게 JWT를 압축하고 해체하는 지를 확인하여라

 

Reading(Parsing) a JWS


아래와 같이 JWS를 읽어보자 ( 파싱해보자 )

1. JwtParserBuilder 객체를 생성하기 위해서 Jwts.parseBuilder() 메서드를 사용한다.

2. JWS 서명을 증명하기 위해 사용하고 싶은 SecretKey 혹은 비대칭 PubliKey를 명세한다.

3. 쓰레드에 안전한 JwtParser를 리턴하기 위해 JwtParserBuilderbuild() 메서드를 호출한다.

4. parseClaimsJws(String) 메서드를 원본 JWS를 만드는 jws String와 함께 호출한다.

5. 파싱이나 서명 유효성이 실패하는 경우에 try/catch 블록 안에서 모든 호출이 래핑된다. 

 

예를 들어,

Jws<Claims> jws;

try {
    jws = Jwts.parserBuilder()  // (1)
    .setSigningKey(key)         // (2)
    .build()                    // (3)
    .parseClaimsJws(jwsString); // (4)
    
    // we can safely trust the JWT
     
catch (JwtException ex) {       // (5)
    
    // we *cannot* use the JWT as intended by its creator
}

주의 : 만약에 JWS를 기대한다면, 항상 JwtPraser의 parseClaimsJws 메서드를 호출하여라 ( 사용 가능한 비슷한 메서드의 한 종류가 아님 ), 왜냐하면 이것은 증명된 JWTS를 파싱하는 정확한 보안 모델을 증명하기 때문이다.

 

Verification Key

 

JWS를 읽을 때 가장 중요한 것은 JWS의 암호화된 signature을 증명하기 위해서 키를 명세하는 것이다. 만약에 signature 증명이 실패하면, JWT는 안전하게 신뢰될 수 없으며 폐기 되어야만 한다.

 

그래서 증명을 위해서 어떠한 키를 사용해야 하는가?

 

만약 jws가 SecretKey로 서명되었다면, 같은 SecretKeyJwtParserBuilder에 명세되어야 한다. 예를 들어 :

Jwts.parserBuilder()
    
  .setSigningKey(secretKey) // <----
  
  .build()
  .parseClaimsJws(jwsString);

만약 jws가 PrivateKey로 서명되었다면, 키에 해당하는 ( PrivateKey 가 아닌 ) PublicKeyJwtParserBuilder에 명세 되어야 한다. 예를 들어,

Jwts.parserBuilder()
    
  .setSigningKey(publicKey) // <---- publicKey, not privateKey
  
  .build()
  .parseClaimsJws(jwsString);

그러나, 이것에 주의해야 한다 - 만약 어플리케이션이 단순한 SecretKey나 KeyPair를 사용하지 않았다면 어떨까? JWS 가 다른 SecretKey들 혹은 공개/비공개 키, 혹은 이것들이 복합된 것으로 생성될 수 있다면? 사용자가 JWT를 조사해보지 않고도 어떤 키가 명세된 키인지 어떻게 알 수 있을까?

 

이러한 경우, JwtParseBuilder, 혹은 setSignKey 메서드를 하나의 키를 호출할 수 없다. - 대신에, SingingKeyResolver를 사용해야 한다.

반응형