부트캠프 기간 동안 JWT에 대해 정확하게 알지 못하고 사용했다.

대충 session 대신에 토큰 기반으로 사용하고 클라이언트에 정보를 저장한다는 정도만 알고 있었다.

이렇게 사용하면 보안에 취약해보이는데 안전하게 사용(?)하는 곳이 많아서 급한대로 사용했다.

그러다가 로그인 파트를 정리하려고 하는데 그때 이해가 되지 않았던 부분들을 해결해야 할 것 같아서 정리한다.

이해가 되지 않았던 부분은 Access token과 Refresh token을 사용하는 방법이었다. 

이때 token은 모두 클라이언트에 저장되는 줄로 잘못 알고 있어서 생겼던 문제였다.

token을 2개를 쓴다고 해서 클라이언트에만 저장을 하는데 이게 왜 안전하지?라는 의문을 가졌다.

그런데 refresh token이 JWT로 구성된다는 오해와 서버에 저장되지 않는다는 부분에서의 문제였다.

아무튼 이해가 되지 않으니 좀 알고 넘어가는 것이 좋을 것 같아서 정리를 하게 됐다.

그러다가 session 방식과의 결합 아닌가 하는 의문도 생겼고 그 부분은 access token, refresh token 정리할 때 추가할 예정이다.

 

1. JWT 란?

JWT(JSON Web Token)은 웹표준(RFC7519)으로서 두 개체에서 JSON 객체를 사용 정보를 안전성 있게 전달하는 방식이다.

  • 특징
    • JWT는 JSON객체를 사용하여 가볍다.
    • JWT는 필요한 모든 정보를 자체적으로 지니고 있다.(self-contained, 자가 수용적)
    • 데이터 하나의 블럭이므로 쉽게 전달될 수 있다.

 

2. JWT 사용하기 ( 자세한 사용법 X )

1) Java JWT Github (github에 들어가면 사용법이 있습니다.)

https://github.com/jwtk/jjwt?tab=readme-ov-file

 

GitHub - jwtk/jjwt: Java JWT: JSON Web Token for Java and Android

Java JWT: JSON Web Token for Java and Android. Contribute to jwtk/jjwt development by creating an account on GitHub.

github.com

 

2) JWT Maven Central 저장소

https://mvnrepository.com/artifact/io.jsonwebtoken/jjwt-api

 

Central 탭의 Version을 누르면 Gradle dependency를 찾을 수 있음

 

3) Example

// 버전마다 사용하는 메서드가 다르므로 주의해야 함


// JWT 생성
private final SecretKey SECRET_KEY = Keys.hmacShaKeyFor("a-string-secret-at-least-256-bits-long");

public String generateJWT(String userRole) {

	// 버전 0.12.6
	return Jwts.builder()
        .claim(KEY_USER_ROLE, userRole)	// private claim
        .issuer("auth")		// registered claim
        .signWith(SECRET_KEY)
        .compact();
        
	// 버전 0.11.5
	//return Jwts.builder()
    //    .claim(KEY_USER_ROLE, userRole)	// private claim
    //    .setIssuer("auth")		// registered claim
    //    .signWith(SECRET_KEY)
    //    .compact();
}

 

// 버전마다 사용하는 메서드가 다르므로 주의해야 함


// JWT 검증
private final SecretKey SECRET_KEY = Keys.hmacShaKeyFor("a-string-secret-at-least-256-bits-long");

public boolean validateToken(String token) {

    // 버전 0.12.6
    Claims payload = Jwts.parser()
    			.verifyWith(SECRET_KEY)
    			.build()
    			.parseSignedClaims(token)
    			.getPayload();
    //byte[] content = Jwts.parser()
    //				.verifyWith(SECRET_KEY)
    //            .build()
    //            .parseSignedContent(token)
    //            .getPayload();
    // String payload = new String(content, StandardCharsets.UITF_8));
    
    
    // 버전 0.11.5
    //Claims payload = Jwts.parserBuilder()
    //            .setSigningKey(SECRET_KEY)
    //            .build()
    //            .parseClaimsJws(token)
    //            .getBody();
    
    
    // registered claim 중 issuer
	String issuer = payload.getIssuer();
    
    // private claim
    String userRole = payload.get(USER_ROLE, String.class);
    
    // ...
}

 

 

3. JWT 구성

'.(온점)'을 구분자로 3가지의 문자열로 구성

(header부) . (payload부) . (signature부)

 

1) Header 부

  • 2가지 정보(타입, 알고리즘)를 지님
    • 타입 : 토큰 타입을 지정. (JWT)
    • 알고리즘 : 해싱 알고리즘을 지정 ( HS256(HMAC SHA256) , RSA )
          Signature 생성시 사용
  • 해당 정보들을 인코딩(base64)을 하여 사용
# 헤더 구성
{"typ":"JWT","alg":"HS256"}

# base64 인코딩값
eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9

2) Payload 부

  • 토큰에 담을 정보가 들어 있음 / Claim들의 집합
  • Claim : 담는 정보의 한 '조각'
  • Cliam의 종류
    • registered claim : 예약된 클레임
      jwts 클레임 메서드 ( 0.12.6 버전 기준)
      - issuer 
      - subject
      - audience
      - expiration
      - notBefore
      - issuedAt
      - id
      메서드로 JWT에 설정되는 값들은 각각 iss, sub, aud, exp, nbf, iat, jti 이다.
    • public claim
      충돌을 방지하기 위해 claim 이름을 URI형식으로
    • private claim
      양측 간에 협의해서 사용하는 claim 이름
# payload 내용
{
  "sub": "1234567890",
  "name": "John Doe",
  "admin": true,
  "iat": 1516239022
}

# base64 인코딩
eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWUsImlhdCI6MTUxNjIzOTAyMn0

※ base64로 인코딩 시, 공백/엔터들이 사라짐
  인코딩 문자열에 '=' 문자가 한두개 붙을 때가 있는데 이 문자를 base64 인코딩 padding문자라고 부름
  이 부분은 제거해도 디코딩할 때 전혀 문제가 되지 않음( 비트변환하면서 끝에 길이를 맞추면서 빈공간을 =으로 채워서 아무 의미 없음 ) - https://en.wikipedia.org/wiki/Base64#Examples
  URL 파라미터로 전달될 때 문제가 되므로 삭제해야 함

 

3) Signature 부

  • 헤더의 인코딩값과 정보의 인코딩값을 합친후 주어진 비밀키로 해쉬 함수를 사용하여 생성
    그후 base64형태로 인코딩하여 사용
# header와 payload를 . 으로 결합
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWUsImlhdCI6MTUxNjIzOTAyMn0

# 결합한 값을 hs256으로 해싱함
# 이때 사용한 secret key == a-string-secret-at-least-256-bits-long
# 그후 base64 인코딩 부분
KMUFsIDTnFmyG3nMiGM6H9FNFUROf3wh7SmqJp-QV30

 

 

4) 결합한 JWT

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWUsImlhdCI6MTUxNjIzOTAyMn0.KMUFsIDTnFmyG3nMiGM6H9FNFUROf3wh7SmqJp-QV30

 

 

 

 

※ JWT 인코더/디코더 사이트

https://jwt.io/

 

JSON Web Tokens - jwt.io

JSON Web Token (JWT) is a compact URL-safe means of representing claims to be transferred between two parties. The claims in a JWT are encoded as a JSON object that is digitally signed using JSON Web Signature (JWS).

jwt.io

 

 

 

참고 사이트 : https://velopert.com/2389

+ Recent posts