JAVA - JWT

JAVA - JWT

Jwts.builder(JwtBuilder) 를 이용해 Token 을 생성하고 Jwts.parserBuilder(JwtParserBuilder) 를 이용해 전달 받은 Token 을 Parsing 한다.

public class JwtMain {

public static void main(String args[]) {

String jwt = Jwts.builder()
.setSubject("test")
.compact();

Jwt<Header, Claims> headerClaimsJwt = Jwts.parserBuilder()
.build()
.parseClaimsJwt(jwt);

// Token 정보를 가져온다.
System.out.println("token : " + jwt);
// JWT Header 정보를 가져온다.
System.out.println("JWT Header : " + headerClaimsJwt.getHeader());
// JWT Body(Claims) 정보를 가져온다.
System.out.println("JWT Claims : " + headerClaimsJwt.getBody());
}
}

Token 에 서명을 진행하지 않았기 때문에 Signature 부분이 빠져있는 것을 확인할 수 있다.

token : eyJhbGciOiJub25lIn0.eyJzdWIiOiJ0ZXN0In0.
JWT Header : {alg=none}
JWT Claims : {sub=test}

JWT 암호화 (JWS)

  • JwtBuilder 객체는 signWith 메소드를 이용해 Key 값과 암호 알고리즘 을 인자값으로 넘겨줘 서명한다.
  • 서명의 유효성은 JwtParserBuilder 객체가 setSigningKey 메소드를 이용해 전달받은 Token 이 유효한지 확인한다.
public class JwsMain {
public static String key = "amF2YS1hcHBsaWNhdGlvbi1zZWN1cmUtc3R1ZHktand0LXNlY3JldGtleS1pcy1zaG91bGQtYmUtYmlnZ2VyLXRoYW4tNTEyYml0cw==";
public static Long tokenValidityInMilliseconds = 100000L;

public static void main(String args[]) {
Key secretKey = Keys.hmacShaKeyFor(key.getBytes());

String jwt = Jwts.builder()
.setSubject("test")
.signWith(secretKey, SignatureAlgorithm.HS512) // JWT 를 암호화 하기 위한 secret 과 알고리즘을 넣어준다.
.compact();

Jws<Claims> claimsJws = Jwts.parserBuilder()
.setSigningKey(secretKey)
.build()
.parseClaimsJws(jwt);

Header header = claimsJws.getHeader();
Claims body = claimsJws.getBody();

System.out.println("token : " + jwt);
System.out.println("JWT Header : " + header);
System.out.println("JWT Claims : " + body);
}
}

Token 에 서명을 했기 때문에 Signature 부분이 추가 돼 있다.

Json Web token = eyJhbGciOiJIUzUxMiJ9.eyJzdWIiOiJEb25nd29vIiwiQXV0aCI6IllhbmciLCJleHAiOjE2Mzg1MjU5NTV9.2EQQCgVcXMIjfHMjl14HpCg9pT6_r5nM7KKOsmSXr7I6IS3I20XpDSXvuVGMU4FQrsXsszYmj8vWAly3ovSb2g
JSON : {sub=Dongwoo, Auth=Yang}

서명 키(Secret) 와 관련된 예외

서명에 들어가는 secret 값을 짧게 하면 다음과 같은 예외가 발생한다.

Exception in thread "main" io.jsonwebtoken.security.WeakKeyException: The specified key byte array is 152 bits which is not secure enough for any JWT HMAC-SHA algorithm. 
The JWT JWA Specification (RFC 7518, Section 3.2) states that keys used with HMAC-SHA algorithms MUST have a size >= 256 bits (the key size must be greater than or equal to the hash output size).

JWT 유효 시간 설정

  • JwtBuilder 객체가 setExpiration 메소드를 이용해 Token 유효시간을 설정한다.
public class JwsTimeMain {
public static String key = "amF2YS1hcHBsaWNhdGlvbi1zZWN1cmUtc3R1ZHktand0LXNlY3JldGtleS1pcy1zaG91bGQtYmUtYmlnZ2VyLXRoYW4tNTEyYml0cw==";
public static String AUTHENTICATION = "Auth";
public static Long tokenValidityInMilliseconds = 100000L;

public static void main(String args[]) throws ParseException {
Key secretKey = Keys.hmacShaKeyFor(key.getBytes());
Date date = new Date(System.currentTimeMillis()+ tokenValidityInMilliseconds);

String jwt = Jwts.builder()
.setSubject("test")
.claim(AUTHENTICATION, "jwt")
.signWith(secretKey, SignatureAlgorithm.HS512)
.setExpiration(date)
.compact();

Jws<Claims> claimsJws = Jwts.parserBuilder()
.setSigningKey(secretKey)
.build()
.parseClaimsJws(jwt);

Header header = claimsJws.getHeader();
Claims body = claimsJws.getBody();

System.out.println("token : " + jwt);
System.out.println("JWT Header : " + header);
System.out.println("JWT Claims : " + body);
System.out.println("expiration time : " + body.get("exp", Date.class));
System.out.println("expiration time : " + body.getExpiration());
System.out.println("sub : " + body.get("sub"));
System.out.println("Auth : " + body.get("Auth"));
}
}

전달 받은 토큰은 JwtParser 객체를 이용해 Parsing 하게 될 때 유효시간을 넘기면 ExpiredJwtException 예외가 발생하게 된다.

Exception in thread "main" io.jsonwebtoken.ExpiredJwtException: JWT expired at 2021-12-04T04:41:07Z. 
Current time: 2021-12-04T04:41:07Z, a difference of 500 milliseconds. Allowed clock skew: 0 milliseconds.

JWT 에서 발생할 수 있는 예외

예외 설명
SignatureException JWT 를 인증하기 위한 검증 secret 이 잘 못 됐을 경우 발생
ExpiredJwtException 토큰의 유효시간이 만료했을 때 발생
UnsupportedJwtException 수신한 JWT 가 Application에서 원하는 형식과 일치하지 않는 경우 발생

테스트 코드를 이용해 확인하기

서명 암 복호화 키가 서로 다른 경우 - SignatureException

@Test
public void create_token_jws_SignatureException() throws InterruptedException {
Key key = Keys.hmacShaKeyFor(secrete.getBytes(StandardCharsets.UTF_8));
Key key2 = Keys.hmacShaKeyFor((secrete+"abcd").getBytes(StandardCharsets.UTF_8));

String token = Jwts.builder()
.setSubject("test")
.signWith(key, SignatureAlgorithm.HS512)
.compact();

assertThrows(SignatureException.class, () -> {
Jwts.parserBuilder()
.setSigningKey(key2)
.build()
.parseClaimsJws(token);
});
}

토큰 시간이 만료된 경우 - ExpiredJwtException

setExpiration 메소드를 이용한 Tokne 유효시간이 지나게 되면 ExpiredJwtException 예외가 발생한다.

Exception in thread "main" io.jsonwebtoken.ExpiredJwtException: JWT expired at 2021-12-03T04:49:50Z. Current time: 2021-12-03T04:49:51Z, a difference of 1163 milliseconds.  Allowed clock skew: 0 milliseconds.
at io.jsonwebtoken.impl.DefaultJwtParser.parse(DefaultJwtParser.java:448)
at io.jsonwebtoken.impl.DefaultJwtParser.parse(DefaultJwtParser.java:550)
at io.jsonwebtoken.impl.DefaultJwtParser.parseClaimsJws(DefaultJwtParser.java:610)
at io.jsonwebtoken.impl.ImmutableJwtParser.parseClaimsJws(ImmutableJwtParser.java:173)
at JwtMain.main(JwtMain.java:31)
@Test
public void create_token_jws_ExpiredJwtException() throws InterruptedException {
Key key = Keys.hmacShaKeyFor(secrete.getBytes(StandardCharsets.UTF_8));
Long tokenValidityInMilliseconds = 100L;
Date date = new Date(System.currentTimeMillis() + tokenValidityInMilliseconds);

String token = Jwts.builder()
.setSubject("test")
.setExpiration(date)
.signWith(key, SignatureAlgorithm.HS512)
.compact();

// token Time Out을 일으키기 위한 Sleep
Thread.sleep(1000L);

assertThrows(ExpiredJwtException.class, () -> {
Jwts.parserBuilder()
.setSigningKey(key)
.build()
.parseClaimsJws(token);
});
}

다른 유형의 Token을 전달 받은 경우 UnsupportedJwtException

  • 전달 받은 Token 과 Parsing 방법이 일치하지 않을 경우 UnsupportedJwtException 예외가 발생 한다.
  • 서명된 JWT(JWS) 를 parseClaimsJwt 메소드를 이용해 Parsing 할 경우 발생한다.
@Test
public void create_token_jws_UnsupportedJwtException(){
Key key = Keys.hmacShaKeyFor(secrete.getBytes(StandardCharsets.UTF_8));

String token = Jwts.builder()
.setSubject("test")
.signWith(key, SignatureAlgorithm.HS512)
.compact();

// JWS 를 parseClaimsJwt 메소드를 이용해 Parsing 할 경우 UnsupportedJwtException 예외가 발생한다.
assertThrows(UnsupportedJwtException.class, () -> {
Jwts.parserBuilder()
.setSigningKey(key)
.build()
.parseClaimsJwt(token);
});
}
Share