알쓸전컴(알아두면 쓸모있는 전자 컴퓨터)

JWT(HS256,RS256)와 JWK(JSON Web Key) 이야기 본문

Web

JWT(HS256,RS256)와 JWK(JSON Web Key) 이야기

백곳 2022. 9. 29. 21:46

 

 

JWT (Json Web Token) 

JWT는 Web에서는 주로 사용자의 인증을 할때 Access Token,Refresh Token 으로 많이 사용 됩니다. 

JWT는 주로 인증을 목적으로 사용 되게 됩니다.

 

 

JWT (HS256)

HS256의 Token의 인증 방식

HS256 알고리즘 인증 방식의 Token 

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.KQ6N4X4AiqAI7RMqD3gbNi-gVgheLz20jAO9gTxshNM

 

 

HS256 방식 토큰 검증 방법

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ

 

 

위의 토큰 Header + PayLoad 부분을 SecretKey을 첨가 하여 Hash 값을 구해 줍니다.

>> https://www.devglan.com/online-tools/hmac-sha256-online  (온라인 HMAC-SHA256 만들어 주는 사이트)

 

 

Hash로 나온 값과 JWT의 Signature 부분이 같으면 JWT 토큰이 변조가 없음을 확인 하게 됩니다.

 

이렇게 JWT Token의 변조가 없음을 확인하는 로직을 통해 다양곳에서 해당 token을 사용하게 됩니다. 

 

대표적으로 로그인한 유저를 인식하는데 사용 하게 됩니다. 

 

만약에 내가 발행한 토큰에 변조 있다면 해킹된 Token 입니다.

 

만약 변조 확인 여부를 검증 하지 않았을때 생기는 문제!

  1. JWT에는 UserId 정보가 들어 있고 해당 UserId로 User을 인식 합니다 이를 통해 다른 사람의 UserId로 변경한다면 다른 유저로 로그인이 가능해요.
  2. JWT에는 보통 권한 정보도 같이 포함해서 Server가 발행 하게 되는데 이 권한을 수정해 버리면 관리자 권한을 얻을수도 있어요.

 

JWT을 유저 인식용도로 사용 했을때 얻는 이점!

  1. Server에서는 유저의 Session 정보를 가지고 있지 않아도 되요. 매번 JWT 토큰을 확인하면 유저 판별 가능!
  2. Server에서 Session을 가지고 있지 않아도 되니 Server간 Session을 공유 하지 않아도 다양한 서버에서 JWT을 통해 로그인 관련되어 쉽게 서버 확장이 가능해요. 예를 들면 트래픽 분산을 위해 동일한 서버 여러대를 뒀을때 서버간 sesstion을 공유하는 작업 없이 쉽게 서버 확장이 가능해요

 

HS256 JWT의 단점!

  1. server에 JWT 토큰 변조 확인을 위해 SecretKey가 필요 합니다. 
  2. 다양한 server에 통합 로그인이 필요할때 모두가 SecretKey가 필요 합니다. 다수의 관리작 SecretKey을 알게 됩니다. SecretKey이 유출 되면  JWT 변조가 가능해 지면서 각종 해킹에 노출이 되요.
  3. 만약 1대의 Server에만 SecretKey을 알고 있고 SecretKey 정보가 없는 타 서버 에서는 서버간 JWT 통신을 통해 변조를 확인 한다면 SecretKey가 없는 서버에 요청을 할때 모든 요청에 서버간 JWT 통신을 하게 되는 자원 낭비가 심각해요.

 

 

JWT(RS256)을 알기전 간단 RSA256 개념

RSA256 개념은 비대칭 키가 핵심이 됩니다. 

 

비대칭 Key는 public,private 키가 쌍으로 짝궁 처럼 존재해요.

 

Private키로 암호화한 Data는 Public key로만 복호화가 가능 해요. 

또는 Public Key로 암호화한 Data 는 Private Key로면 복호화 가능해요.

 

핵심은 원리는

1. Private Key으로 암호화 한건 Private Key 으로 복호화 하지 못하고 Public Key 으로만 복호화 가능

2. Public 을 통해 암호화 한값은 Public Key 로 복호화 하지 못합니다. 

 

위의 2가지 핵심 원리를 통해 RS256 알고리즘을 알아 볼게요

JWT RS256 알고리즘의 필요성

Login Server 1대 하고만 통신을 한다면 위와 사용 되고 RS256의 이점 거의 없습니다. 

 

하지만 Server가 여러에서 오는  HS256의 문제점 을 해결 할수 있습니다. 

 

HS256의 문제점

1. 서버간 인증 통신을 줄이고자 SecertKey 모든 서버에 알려지면 보안에 위험이 크다. 

2. 1대의 서버만 SecretKey을 가지고 있고 통신으로 이를 해결 하자니 통신 Loss가 너무 크다. 

 

RS256에서 큰그림 에서 인증 방식 

 

 

Login Server 와는 1번만 통신 하며 SecertKey 을 공유하지 않아도 됩니다. 

 

이를 위해서 어떻게 RS256 알고리즘이 데이터 변조를 확인을 하는지 알아 보겠습니다. 

 

RS256 방식 토큰 검증 방법

RS256 알고리즘 인증 방식의 Token 

 

eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICI2OVdjMG5DbHo2N0dUMXlBNnIzYlV3bzRGS2pkR3RNS3ZHandFdDN4MkY0In0.eyJleHAiOjE2NjQ0NjQwNTMsImlhdCI6MTY2NDQ2Mzc1MywiYXV0aF90aW1lIjoxNjY0NDYzNzM0LCJqdGkiOiIxMTVlN2QyOS0wOGJlLTQ2YWQtYjY3My1kNWJlZTg1ODk1NzAiLCJpc3MiOiJodHRwOi8vbG9jYWxob3N0OjgwODAvcmVhbG1zL3Rlc3QiLCJhdWQiOiJhY2NvdW50Iiwic3ViIjoiYzYyNjBhYWItZjNlZC00MWUyLTk2ODUtMDcwMDRlMDRlZmFhIiwidHlwIjoiQmVhcmVyIiwiYXpwIjoidGVzdC1jbGllbnQtaWQiLCJzZXNzaW9uX3N0YXRlIjoiZGRjYzRkNjAtZTgxYy00ZmYwLWFhMDgtNmZmYzdkZDZhYmNkIiwiYWNyIjoiMSIsInJlYWxtX2FjY2VzcyI6eyJyb2xlcyI6WyJkZWZhdWx0LXJvbGVzLXRlc3QiLCJvZmZsaW5lX2FjY2VzcyIsInVtYV9hdXRob3JpemF0aW9uIl19LCJyZXNvdXJjZV9hY2Nlc3MiOnsiYWNjb3VudCI6eyJyb2xlcyI6WyJtYW5hZ2UtYWNjb3VudCIsIm1hbmFnZS1hY2NvdW50LWxpbmtzIiwidmlldy1wcm9maWxlIl19fSwic2NvcGUiOiJvcGVuaWQgcHJvZmlsZSBlbWFpbCIsInNpZCI6ImRkY2M0ZDYwLWU4MWMtNGZmMC1hYTA4LTZmZmM3ZGQ2YWJjZCIsImVtYWlsX3ZlcmlmaWVkIjpmYWxzZSwibmFtZSI6InRlc3QgdGVzdCIsInByZWZlcnJlZF91c2VybmFtZSI6InRlc3R1c2VyIiwiZ2l2ZW5fbmFtZSI6InRlc3QiLCJmYW1pbHlfbmFtZSI6InRlc3QifQ.FrtUVgcH8tPrLNRMsuCLfSBbToTX4gKDIZg283AXZRgJSPSdKySihRO83lNvoPc_Ndv0XTnyX6ZfZIPxpwaVHJTOJBTyrtPOFDVkqx01Z9Jiw94gBDm29gzheVsHVbg82YWVQx-tqle1fWNXH4zP5PjHEPK5uh419U5dr7_V51dkrLMMIY3pDXoMBMD85a7fBCSdpaHmduXFCdp3-6O5QPJ3KCDLFz4nPUTykke90189rNraZPhfvb-SLbtxDk2LXHf-BLwh11AjWQRapuWOoDCg3fKKvXIUaIuSo438xTCk6xQhiKl3p8_ECMrC_T04uUBDGMfpq1nRycWcS8OXyA

 

 

위와 같이 Token이 분할 되게 되는데 자세히 알아봐야 부분이 검증 입니다. 

 

RS256에서 검증 부분의 토큰이 어떻게 만들어 지는 알아 보겠습니다 .

먼저 Header 와 Payload 를 가지고 SHA256 Hash 값을 만듭니다. 

 

 

 

>>  https://emn178.github.io/online-tools/sha256.html   (sha256 Hash 만드는 사이트)

 

그리고 아래 처럼 hax 값으로 3031300d060960864801650304020105000420 뒤에 SHA256 값인  8046701952971a6b7251d273a37194272a443bde37086f3b9941898eb364b937 을 붙혀 줍니다. 

 

그럼 완성되는건 아래와 같습니다. 

3031300d0609608648016503040201050004208046701952971a6b7251d273a37194272a443bde37086f3b9941898eb364b937

사실 이건 ASN.1 이라고 하는 데이터 표현 방법중 DER이라는 표현 방법을로 인코딩 한것인데 

SHA256 값을 Wrap 해준 것 입니다. 

 

ASN.1 Decode 로 해당 값을 Decoding 해주면 

>> https://lapo.it/asn1js/  (ASN.1 Decode)

 

해당 값 3031300d0609608648016503040201050004208046701952971a6b7251d273a37194272a443bde37086f3b9941898eb364b937

 

이렇게 검증 부분이 JWT 에서 만들어 지게 됩니다. 

 

JWT(RS256)을 검증 하는 서버 로직

 

 

위와 같이 JWT을 변조을 검증하는 Server에서는 public Key만 존재 하면 

Singature 부분을 복호화 하고 그안에 들어 있는 SHA256 값과 JWT의 haeder+payload 데이터로 얻은 SHA256 값을 

비교 하여 변조 검증을 할수 있습니다. 

 

그럼 JWT(RS256)에 검증의 필요한 Public Key은 어디서 얻어 받아 오는지에 대한 궁금증이 남았습니다. 

 

그건 인증 서버에서 제공 하는 JWK을 통해 Public Key을 받아 올수 있습니다. 

인증 서버에 보면 JWK을 제공 하는 URL을 알려주고 해당 URL에 들어가면 JSON 데이터가 있습니다. 

Public Key JWK 에 대해서

JWK 는 위의 RS256의 검증에 필요한  public Key을 검증하는 Server에서 받기 위해서 필요한 데이터입니다. 

 

보통 아래와 같은 JSON 객체를 제공 합니다. 

{
   "keys":[
      {
         "kid":"69Wc0nClz67GT1yA6r3bUwo4FKjdGtMKvGjwEt3x2F4",
         "kty":"RSA",
         "alg":"RS256",
         "use":"sig",
         "n":"nU5RinR1qIWrrztc4XTpxtmhEFQQgIqcWZ8WKVYjJkCgjmWE6gOCGD8CIm-RGZv2yXlfXpVU2v_PmqyowSWUmfs6L1Lbtr7elcfxoNfW-aTmJZ-Ukak_fzD0T_fZjJqHMHXva5G3T0_-VtPEwAyNUd6QYvVmTqgR7Fp_8EvvRF8FT1RVxnqompZGm8DrDBrx_teWnHkEQuiUvjpd9TTxZTWEZ7nfcb886ZvIXGN4AvQteyzoUtriDeXSNLuSE5_8c9dGf0pveVAo1SFdrDR2ORxVgZVjGcGol3H_4ysxlNe52jFNfX636CP9Owvb3Lm_hi7r0rB69uI_dq6Nt8VLSw",
         "e":"AQAB",
         "x5c":[
            "MIIClzCCAX8CBgGDhD7igDANBgkqhkiG9w0BAQsFADAPMQ0wCwYDVQQDDAR0ZXN0MB4XDTIyMDkyODEzMTU1N1oXDTMyMDkyODEzMTczN1owDzENMAsGA1UEAwwEdGVzdDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAJ1OUYp0daiFq687XOF06cbZoRBUEICKnFmfFilWIyZAoI5lhOoDghg/AiJvkRmb9sl5X16VVNr/z5qsqMEllJn7Oi9S27a+3pXH8aDX1vmk5iWflJGpP38w9E/32YyahzB172uRt09P/lbTxMAMjVHekGL1Zk6oEexaf/BL70RfBU9UVcZ6qJqWRpvA6wwa8f7Xlpx5BELolL46XfU08WU1hGe533G/POmbyFxjeAL0LXss6FLa4g3l0jS7khOf/HPXRn9Kb3lQKNUhXaw0djkcVYGVYxnBqJdx/+MrMZTXudoxTX1+t+gj/TsL29y5v4Yu69KwevbiP3aujbfFS0sCAwEAATANBgkqhkiG9w0BAQsFAAOCAQEASQt/gfHzRLMhAWKI8lXINlQwx93atlQuFfS8F60E9qzRu23ldJ68OGa8LSTIMUdat4kdvsdlAui0LbF8qRUNOLh2n+tIIUQOio+JAPZzq1zAmeo4kxUqESI45u5S1rEDzqqR0ScwgwGm3r66bRblVX+Znko9A9u7HUl5RxScw8AMHx8E34BRjAGcydWoliMQUB4UFKoF95neMNgRY3OtovhnbZ5a55orCUPDcDG1T9LnZVQPThkZuaheGwFQyVYNbMZ6ee5dGhOa1IolgrLKj8i6Uj/TqGrGanMIY9Mt5mcIpFkY1ixCZhD2I2ePsvKU/HAO9pGW0NpXDSVodMijWQ=="
         ],
         "x5t":"r9ENrGrTQZkIQ8EXnUeJeFhkPgo",
         "x5t#S256":"lHTBY82JqpYfYmL1LUMKJVFGtw2GRBN_dSYHIlJphLE"
      },
      {
         "kid":"q2-NGem17yo36TKLmNz7uy61Tdv3R772ifCHl20H8io",
         "kty":"RSA",
         "alg":"RSA-OAEP",
         "use":"enc",
         "n":"w2rdgKXmwMLjNTwEkXC7hjJzxDHcAQ2udtKSsWMRpL4ES-ZK2261SO4qzV1bJltHV-F9VrqI1-I85UJwIrnqLdCUVRSOlgs12QURn8p8E74Xfy0SMXjdjEQ_RADTZPGXngLrEIVSojN6iCTptwuCV8wtO-spaBi9F3n9PzXT83X_LlNzASFnyF1_qAXNk7meEbMtBmd3UGhP0uzOKUhLydKZlYClJYyQslcq0eF_zweFqxpYLFMHD_BeIHjPNHFSuxmQfKzs6jrOsq7WrsveunLW0_M9cppUOH5rlvLQ68Cass37jglwdEUzBriXmiB0vuRXZ9OCyVSCoydKiJpb-w",
         "e":"AQAB",
         "x5c":[
            "MIIClzCCAX8CBgGDhD7j9zANBgkqhkiG9w0BAQsFADAPMQ0wCwYDVQQDDAR0ZXN0MB4XDTIyMDkyODEzMTU1N1oXDTMyMDkyODEzMTczN1owDzENMAsGA1UEAwwEdGVzdDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMNq3YCl5sDC4zU8BJFwu4Yyc8Qx3AENrnbSkrFjEaS+BEvmSttutUjuKs1dWyZbR1fhfVa6iNfiPOVCcCK56i3QlFUUjpYLNdkFEZ/KfBO+F38tEjF43YxEP0QA02Txl54C6xCFUqIzeogk6bcLglfMLTvrKWgYvRd5/T810/N1/y5TcwEhZ8hdf6gFzZO5nhGzLQZnd1BoT9LszilIS8nSmZWApSWMkLJXKtHhf88HhasaWCxTBw/wXiB4zzRxUrsZkHys7Oo6zrKu1q7L3rpy1tPzPXKaVDh+a5by0OvAmrLN+44JcHRFMwa4l5ogdL7kV2fTgslUgqMnSoiaW/sCAwEAATANBgkqhkiG9w0BAQsFAAOCAQEACE7stD5QhPWMsGwkk5tRY45Kj2Cb6ART6LZ29C1PjH9kmbOp3WKiHuGrGePy9nKPjIvC7o6cNpu0onZvsZcoIRTElbM27g18eam/0f6t+KVBlFPvwA5oij3NO5uicweuZDjWZAXL7zDCQqKPnDYVRGMG5jHfZcXk9RUh3+cyLnUReSmRRcsD8MhfQFV5qB0pqU3BvhsOj7v4SLTIjaZUsVVblLnocFqIfLGg2eU3ocMKomnzDVS7/4h2MFq/+NtmK3FPiIv0ac82p+SegO5E84awf34SZh9s5gpFFfALj8Fv4fHrPRGWKx6AphUxu+ix/DFSw7NRJmHUWLLwkNEDow=="
         ],
         "x5t":"74QnbAjwIlpxWpgwOnktvR40CCE",
         "x5t#S256":"V7FoJFGkq8u9zoWj9FLCp2Sr3U1Ww08RXkQpekaTiSU"
      }
   ]
}

여기서 보면 이전에 RS256에 JWT 은 kid가 존재 했는데 존재의 이유를 JWK 에서 알수 있습니다. 

제공 되는 공개키중 kid와 일치하는 JSON 객체을 선택 하면 됩니다. 

 

그럼 위의 JWK 데이터 에서 공개키는 바로  n(modules),e(exponent) 값 입니다. 

"n":"nU5RinR1qIWrrztc4XTpxtmhEFQQgIqcWZ8WKVYjJkCgjmWE6gOCGD8CIm-RGZv2yXlfXpVU2v_PmqyowSWUmfs6L1Lbtr7elcfxoNfW-aTmJZ-Ukak_fzD0T_fZjJqHMHXva5G3T0_-VtPEwAyNUd6QYvVmTqgR7Fp_8EvvRF8FT1RVxnqompZGm8DrDBrx_teWnHkEQuiUvjpd9TTxZTWEZ7nfcb886ZvIXGN4AvQteyzoUtriDeXSNLuSE5_8c9dGf0pveVAo1SFdrDR2ORxVgZVjGcGol3H_4ysxlNe52jFNfX636CP9Owvb3Lm_hi7r0rB69uI_dq6Nt8VLSw"

 

"e":"AQAB" 

 

그럼 해당 데이터로 공개키를 JAVA API로 만드는 방법을 알아 보겠습니다. 

 

String modulus = "nU5RinR1qIWrrztc4XTpxtmhEFQQgIqcWZ8WKVYjJkCgjmWE6gOCGD8CIm-RGZv2yXlfXpVU2v_PmqyowSWUmfs6L1Lbtr7elcfxoNfW-aTmJZ-Ukak_fzD0T_fZjJqHMHXva5G3T0_-VtPEwAyNUd6QYvVmTqgR7Fp_8EvvRF8FT1RVxnqompZGm8DrDBrx_teWnHkEQuiUvjpd9TTxZTWEZ7nfcb886ZvIXGN4AvQteyzoUtriDeXSNLuSE5_8c9dGf0pveVAo1SFdrDR2ORxVgZVjGcGol3H_4ysxlNe52jFNfX636CP9Owvb3Lm_hi7r0rB69uI_dq6Nt8VLSw";

 byte[] n = Base64.getUrlDecoder().decode(modulus);
 byte[] e = Base64.getUrlDecoder().decode("AQAB");

 BigInteger bigModulur = new BigInteger(1, n);
 BigInteger bigExponent = new BigInteger(1, e);
 RSAPublicKeySpec spec = new RSAPublicKeySpec(bigModulur, bigExponent);

 KeyFactory factory = KeyFactory.getInstance("RSA");
 //publicKey 생성
 PublicKey pub = factory.generatePublic(spec);

 

그럼 최종으로 JAVA 코드로 public Key로 부터 JWT 변조 검사 까지 체크 하는 코드를 작성해 보겠습니다.

import sun.security.util.DerInputStream;
import sun.security.util.DerValue;

import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.math.BigInteger;
import java.nio.charset.StandardCharsets;
import java.security.*;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.RSAPublicKeySpec;
import java.util.Base64;


public class Main {
    public static void main(String[] args) throws IOException, NoSuchAlgorithmException, InvalidKeySpecException, NoSuchPaddingException, InvalidKeyException, IllegalBlockSizeException, BadPaddingException {

        String modulus = "nU5RinR1qIWrrztc4XTpxtmhEFQQgIqcWZ8WKVYjJkCgjmWE6gOCGD8CIm-RGZv2yXlfXpVU2v_PmqyowSWUmfs6L1Lbtr7elcfxoNfW-aTmJZ-Ukak_fzD0T_fZjJqHMHXva5G3T0_-VtPEwAyNUd6QYvVmTqgR7Fp_8EvvRF8FT1RVxnqompZGm8DrDBrx_teWnHkEQuiUvjpd9TTxZTWEZ7nfcb886ZvIXGN4AvQteyzoUtriDeXSNLuSE5_8c9dGf0pveVAo1SFdrDR2ORxVgZVjGcGol3H_4ysxlNe52jFNfX636CP9Owvb3Lm_hi7r0rB69uI_dq6Nt8VLSw";
        byte[] n = Base64.getUrlDecoder().decode(modulus);
        byte[] e = Base64.getUrlDecoder().decode("AQAB");
        BigInteger bigModulur = new BigInteger(1, n);
        BigInteger bigExponent = new BigInteger(1, e);
        RSAPublicKeySpec spec = new RSAPublicKeySpec(bigModulur, bigExponent);
        KeyFactory factory = KeyFactory.getInstance("RSA");
        //PublicKey 생성
        PublicKey pub = factory.generatePublic(spec);
        Cipher decryptCipher = Cipher.getInstance("RSA");
        decryptCipher.init(Cipher.DECRYPT_MODE, pub);

        String Base64Signature = "FrtUVgcH8tPrLNRMsuCLfSBbToTX4gKDIZg283AXZRgJSPSdKySihRO83lNvoPc_Ndv0XTnyX6ZfZIPxpwaVHJTOJBTyrtPOFDVkqx01Z9Jiw94gBDm29gzheVsHVbg82YWVQx-tqle1fWNXH4zP5PjHEPK5uh419U5dr7_V51dkrLMMIY3pDXoMBMD85a7fBCSdpaHmduXFCdp3-6O5QPJ3KCDLFz4nPUTykke90189rNraZPhfvb-SLbtxDk2LXHf-BLwh11AjWQRapuWOoDCg3fKKvXIUaIuSo438xTCk6xQhiKl3p8_ECMrC_T04uUBDGMfpq1nRycWcS8OXyA";
        byte[] signature = Base64.getUrlDecoder().decode(Base64Signature);
        //복호화된 Data
        byte[] decryptedMessageBytes = decryptCipher.doFinal(signature);

        DerInputStream in = new DerInputStream(decryptedMessageBytes, 0, decryptedMessageBytes.length, false);
        //https://lapo.it/asn1js/#MDEwDQYJYIZIAWUDBAIBBQAEINDs2hSwvhN3GNR5wRN9YkkC8XZvEt5uX0y9vgAA_sLu
        System.out.println("복호회된 데이터 = " + byteArrayToHex(decryptedMessageBytes));
        DerValue[] sequence = in.getSequence(2);
        byte[] decodeSig = sequence[1].getOctetString();
        System.out.println("복호회된 안에 있는 SHA256 Hash 값 = " + byteArrayToHex(decodeSig));


        //Header와PayLoad
        String headerAndPayload = "eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICI2OVdjMG5DbHo2N0dUMXlBNnIzYlV3bzRGS2pkR3RNS3ZHandFdDN4MkY0In0.eyJleHAiOjE2NjQ0NjQwNTMsImlhdCI6MTY2NDQ2Mzc1MywiYXV0aF90aW1lIjoxNjY0NDYzNzM0LCJqdGkiOiIxMTVlN2QyOS0wOGJlLTQ2YWQtYjY3My1kNWJlZTg1ODk1NzAiLCJpc3MiOiJodHRwOi8vbG9jYWxob3N0OjgwODAvcmVhbG1zL3Rlc3QiLCJhdWQiOiJhY2NvdW50Iiwic3ViIjoiYzYyNjBhYWItZjNlZC00MWUyLTk2ODUtMDcwMDRlMDRlZmFhIiwidHlwIjoiQmVhcmVyIiwiYXpwIjoidGVzdC1jbGllbnQtaWQiLCJzZXNzaW9uX3N0YXRlIjoiZGRjYzRkNjAtZTgxYy00ZmYwLWFhMDgtNmZmYzdkZDZhYmNkIiwiYWNyIjoiMSIsInJlYWxtX2FjY2VzcyI6eyJyb2xlcyI6WyJkZWZhdWx0LXJvbGVzLXRlc3QiLCJvZmZsaW5lX2FjY2VzcyIsInVtYV9hdXRob3JpemF0aW9uIl19LCJyZXNvdXJjZV9hY2Nlc3MiOnsiYWNjb3VudCI6eyJyb2xlcyI6WyJtYW5hZ2UtYWNjb3VudCIsIm1hbmFnZS1hY2NvdW50LWxpbmtzIiwidmlldy1wcm9maWxlIl19fSwic2NvcGUiOiJvcGVuaWQgcHJvZmlsZSBlbWFpbCIsInNpZCI6ImRkY2M0ZDYwLWU4MWMtNGZmMC1hYTA4LTZmZmM3ZGQ2YWJjZCIsImVtYWlsX3ZlcmlmaWVkIjpmYWxzZSwibmFtZSI6InRlc3QgdGVzdCIsInByZWZlcnJlZF91c2VybmFtZSI6InRlc3R1c2VyIiwiZ2l2ZW5fbmFtZSI6InRlc3QiLCJmYW1pbHlfbmFtZSI6InRlc3QifQ";
        byte[] bytes = headerAndPayload.getBytes(StandardCharsets.UTF_8);
        MessageDigest md = MessageDigest.getInstance("SHA-256");
        md.update(bytes);
        //Header와PayLoad으로 계산한 sha256Hash 값
        byte[] headerAndPayloadSha256Hash = md.digest();
        System.out.println("Header와PayLoad으로 계산한 sha256Hash 값 = " + byteArrayToHex(headerAndPayloadSha256Hash));

        System.out.println("변조가 되지 않는 토큰 인가요? " + MessageDigest.isEqual(headerAndPayloadSha256Hash, decodeSig));
    }

    public static String byteArrayToHex(byte[] a) {
        StringBuilder sb = new StringBuilder(a.length * 2);
        for (byte b : a)
            sb.append(String.format("%02x", b));
        return sb.toString();
    }
}

 

출력 결과

복호회된 데이터 = 3031300d0609608648016503040201050004208046701952971a6b7251d273a37194272a443bde37086f3b9941898eb364b937
복호회된 안에 있는 SHA256 Hash 값 = 8046701952971a6b7251d273a37194272a443bde37086f3b9941898eb364b937
Header와PayLoad으로 계산한 sha256Hash 값 = 8046701952971a6b7251d273a37194272a443bde37086f3b9941898eb364b937
변조가 되지 않는 토큰 인가요? true

 

이상으로 JWT RS256 와 JWK의 관계를 정리해 보았습니다. 

 

해당 부분은 Oauth2 와 긴밀하게 관련이 있습니다. 

Oauth2 자료 정리시에 문서로만으로 어렵기때문에 동영상을 제작하여 JWT JWK RS256에

실제 사용 하는 실습을 진행 하겠습니다.

 

Comments