JWT를 만들고 확인하고 싶습니다.

Tink의 JWT 라이브러리를 사용하면 JSON 웹 토큰 (JWT)을 생성하고 확인할 수 있습니다.

JWT 만들기

대부분의 사용 사례에서는 JWT_ES256 키 유형과 함께 JwtPublicKeySign 기본 요소를 사용하는 것이 좋습니다.

다음 예에서는 JWT를 만드는 방법과 공개 키 세트를 JWK 세트 형식으로 변환하는 방법을 보여줍니다.

C++

// An example for signing JSON Web Tokens (JWT).
#include <iostream>
#include <memory>
#include <ostream>
#include <string>
#include <utility>

#include "absl/flags/flag.h"
#include "absl/flags/parse.h"
#include "absl/log/check.h"
#include "absl/strings/string_view.h"
#include "absl/time/clock.h"
#include "absl/time/time.h"
#include "tink/config/global_registry.h"
#include "util/util.h"
#include "tink/jwt/jwt_public_key_sign.h"
#include "tink/jwt/jwt_signature_config.h"
#include "tink/jwt/raw_jwt.h"
#include "tink/keyset_handle.h"
#include "tink/util/status.h"

ABSL_FLAG(std::string, keyset_filename, "", "Keyset file in JSON format");
ABSL_FLAG(std::string, audience, "", "Expected audience in the token");
ABSL_FLAG(std::string, token_filename, "", "Path to the token file");

namespace {

using ::crypto::tink::JwtPublicKeySign;
using ::crypto::tink::KeysetHandle;
using ::crypto::tink::RawJwt;
using ::crypto::tink::RawJwtBuilder;
using ::crypto::tink::util::Status;
using ::crypto::tink::util::StatusOr;

void ValidateParams() {
  // ...
}

}  // namespace

namespace tink_cc_examples {

// JWT sign example CLI implementation.
Status JwtSign(const std::string& keyset_filename, absl::string_view audience,
               const std::string& token_filename) {
  Status result = crypto::tink::JwtSignatureRegister();
  if (!result.ok()) return result;

  // Read the keyset from file.
  StatusOr<std::unique_ptr<KeysetHandle>> keyset_handle =
      ReadJsonCleartextKeyset(keyset_filename);
  if (!keyset_handle.ok()) return keyset_handle.status();
  StatusOr<RawJwt> raw_jwt =
      RawJwtBuilder()
          .AddAudience(audience)
          .SetExpiration(absl::Now() + absl::Seconds(100))
          .Build();
  if (!raw_jwt.ok()) return raw_jwt.status();
  StatusOr<std::unique_ptr<JwtPublicKeySign>> jwt_signer =
      (*keyset_handle)
          ->GetPrimitive<crypto::tink::JwtPublicKeySign>(
              crypto::tink::ConfigGlobalRegistry());
  if (!jwt_signer.ok()) return jwt_signer.status();

  StatusOr<std::string> token = (*jwt_signer)->SignAndEncode(*raw_jwt);
  if (!token.ok()) return token.status();

  return WriteToFile(*token, token_filename);
}

}  // namespace tink_cc_examples

int main(int argc, char** argv) {
  absl::ParseCommandLine(argc, argv);

  ValidateParams();

  std::string keyset_filename = absl::GetFlag(FLAGS_keyset_filename);
  std::string audience = absl::GetFlag(FLAGS_audience);
  std::string token_filename = absl::GetFlag(FLAGS_token_filename);

  std::clog << "Using keyset in " << keyset_filename << " to ";
  std::clog << " generate and sign a token using audience '" << audience
            << "'; the resulting signature is written to " << token_filename
            << '\n';

  CHECK_OK(
      tink_cc_examples::JwtSign(keyset_filename, audience, token_filename));
  return 0;
}

Go

func Example_signAndVerify() {
	// A private keyset created with
	// "tinkey create-keyset --key-template=JWT_ES256 --out private_keyset.cfg".
	// Note that this keyset has the secret key information in cleartext.
	privateJSONKeyset := `{
		"primaryKeyId": 1742360595,
		"key": [
			{
				"keyData": {
					"typeUrl": "type.googleapis.com/google.crypto.tink.JwtEcdsaPrivateKey",
					"value": "GiBgVYdAPg3Fa2FVFymGDYrI1trHMzVjhVNEMpIxG7t0HRJGIiBeoDMF9LS5BDCh6YgqE3DjHwWwnEKEI3WpPf8izEx1rRogbjQTXrTcw/1HKiiZm2Hqv41w7Vd44M9koyY/+VsP+SAQAQ==",
					"keyMaterialType": "ASYMMETRIC_PRIVATE"
				},
				"status": "ENABLED",
				"keyId": 1742360595,
				"outputPrefixType": "TINK"
			}
		]
	}`

	// The corresponding public keyset created with
	// "tinkey create-public-keyset --in private_keyset.cfg"
	publicJSONKeyset := `{
		"primaryKeyId": 1742360595,
		"key": [
			{
				"keyData": {
					"typeUrl": "type.googleapis.com/google.crypto.tink.JwtEcdsaPublicKey",
					"value": "EAEaIG40E1603MP9RyoomZth6r+NcO1XeODPZKMmP/lbD/kgIiBeoDMF9LS5BDCh6YgqE3DjHwWwnEKEI3WpPf8izEx1rQ==",
					"keyMaterialType": "ASYMMETRIC_PUBLIC"
				},
				"status": "ENABLED",
				"keyId": 1742360595,
				"outputPrefixType": "TINK"
			}
		]
	}`

	// Create a keyset handle from the cleartext private keyset in the previous
	// step. The keyset handle provides abstract access to the underlying keyset to
	// limit the access of the raw key material. WARNING: In practice,
	// it is unlikely you will want to use a insecurecleartextkeyset, as it implies
	// that your key material is passed in cleartext, which is a security risk.
	// Consider encrypting it with a remote key in Cloud KMS, AWS KMS or HashiCorp Vault.
	// See https://github.com/google/tink/blob/master/docs/GOLANG-HOWTO.md#storing-and-loading-existing-keysets.
	privateKeysetHandle, err := insecurecleartextkeyset.Read(
		keyset.NewJSONReader(bytes.NewBufferString(privateJSONKeyset)))
	if err != nil {
		log.Fatal(err)
	}

	// Retrieve the JWT Signer primitive from privateKeysetHandle.
	signer, err := jwt.NewSigner(privateKeysetHandle)
	if err != nil {
		log.Fatal(err)
	}

	// Use the primitive to create and sign a token. In this case, the primary key of the
	// keyset will be used (which is also the only key in this example).
	expiresAt := time.Now().Add(time.Hour)
	audience := "example audience"
	subject := "example subject"
	rawJWT, err := jwt.NewRawJWT(&jwt.RawJWTOptions{
		Audience:  &audience,
		Subject:   &subject,
		ExpiresAt: &expiresAt,
	})
	if err != nil {
		log.Fatal(err)
	}
	token, err := signer.SignAndEncode(rawJWT)
	if err != nil {
		log.Fatal(err)
	}

	// Create a keyset handle from the keyset containing the public key. Because the
	// public keyset does not contain any secrets, we can use [keyset.ReadWithNoSecrets].
	publicKeysetHandle, err := keyset.ReadWithNoSecrets(
		keyset.NewJSONReader(bytes.NewBufferString(publicJSONKeyset)))
	if err != nil {
		log.Fatal(err)
	}

	// Retrieve the Verifier primitive from publicKeysetHandle.
	verifier, err := jwt.NewVerifier(publicKeysetHandle)
	if err != nil {
		log.Fatal(err)
	}

	// Verify the signed token.
	validator, err := jwt.NewValidator(&jwt.ValidatorOpts{ExpectedAudience: &audience})
	if err != nil {
		log.Fatal(err)
	}
	verifiedJWT, err := verifier.VerifyAndDecode(token, validator)
	if err != nil {
		log.Fatal(err)
	}

	// Extract subject claim from the token.
	if !verifiedJWT.HasSubject() {
		log.Fatal(err)
	}
	extractedSubject, err := verifiedJWT.Subject()
	if err != nil {
		log.Fatal(err)
	}
	fmt.Println(extractedSubject)
	// Output: example subject
}

자바

package jwt;

import static java.nio.charset.StandardCharsets.UTF_8;

import com.google.crypto.tink.InsecureSecretKeyAccess;
import com.google.crypto.tink.KeysetHandle;
import com.google.crypto.tink.RegistryConfiguration;
import com.google.crypto.tink.TinkJsonProtoKeysetFormat;
import com.google.crypto.tink.jwt.JwtPublicKeySign;
import com.google.crypto.tink.jwt.JwtSignatureConfig;
import com.google.crypto.tink.jwt.RawJwt;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.time.Instant;

/**
 * A command-line utility for signing JSON Web Tokens (JWTs).
 *
 * <p>It loads cleartext private keys from disk - this is not recommended!
 *
 * <p>It requires the following arguments:
 *
 * <ul>
 *   <li>private-keyset-file: Name of the input file containing the private keyset.
 *   <li>audience: The audience claim to be used in the token
 *   <li>token-file: name of the output file containing the signed JWT.
 */
public final class JwtSign {
  public static void main(String[] args) throws Exception {
    if (args.length != 3) {
      System.err.printf("Expected 3 parameters, got %d\n", args.length);
      System.err.println("Usage: java JwtSign private-keyset-file audience token-file");
      System.exit(1);
    }

    Path privateKeysetFile = Paths.get(args[0]);
    String audience = args[1];
    Path tokenFile = Paths.get(args[2]);

    // Register all JWT signature key types with the Tink runtime.
    JwtSignatureConfig.register();

    // Read the private keyset into a KeysetHandle.
    KeysetHandle privateKeysetHandle =
        TinkJsonProtoKeysetFormat.parseKeyset(
            new String(Files.readAllBytes(privateKeysetFile), UTF_8),
            InsecureSecretKeyAccess.get());

    // Get the primitive.
    JwtPublicKeySign signer =
        privateKeysetHandle.getPrimitive(RegistryConfiguration.get(), JwtPublicKeySign.class);

    // Use the primitive to sign a token that expires in 100 seconds.
    RawJwt rawJwt =
        RawJwt.newBuilder()
            .addAudience(audience)
            .setExpiration(Instant.now().plusSeconds(100))
            .build();
    String signedToken = signer.signAndEncode(rawJwt);
    Files.write(tokenFile, signedToken.getBytes(UTF_8));
  }

  private JwtSign() {}
}

Python

"""A utility for creating and signing JSON Web Tokens (JWT).

It loads cleartext keys from disk - this is not recommended!
"""

import datetime

from absl import app
from absl import flags
from absl import logging
import tink
from tink import jwt
from tink import secret_key_access


_PRIVATE_KEYSET_PATH = flags.DEFINE_string(
    'private_keyset_path', None,
    'Path to the keyset used for the JWT signature operation.')
_AUDIENCE = flags.DEFINE_string('audience', None,
                                'Audience to be used in the token')
_TOKEN_PATH = flags.DEFINE_string('token_path', None, 'Path to the token file.')


def main(argv):
  del argv  # Unused.

  # Initialise Tink
  jwt.register_jwt_signature()

  # Read the keyset into a KeysetHandle
  with open(_PRIVATE_KEYSET_PATH.value, 'rt') as keyset_file:
    try:
      text = keyset_file.read()
      keyset_handle = tink.json_proto_keyset_format.parse(
          text, secret_key_access.TOKEN
      )
    except tink.TinkError as e:
      logging.exception('Error reading keyset: %s', e)
      return 1

  now = datetime.datetime.now(tz=datetime.timezone.utc)

  # Get the JwtPublicKeySign primitive
  try:
    jwt_sign = keyset_handle.primitive(jwt.JwtPublicKeySign)
  except tink.TinkError as e:
    logging.exception('Error creating JwtPublicKeySign: %s', e)
    return 1

  # Create token
  raw_jwt = jwt.new_raw_jwt(
      audiences=[_AUDIENCE.value],
      expiration=now + datetime.timedelta(seconds=100))
  token = jwt_sign.sign_and_encode(raw_jwt)
  with open(_TOKEN_PATH.value, 'wt') as token_file:
    token_file.write(token)
  logging.info('Token has been written to %s', _TOKEN_PATH.value)


if __name__ == '__main__':
  flags.mark_flags_as_required(
      ['private_keyset_path', 'audience', 'token_path'])
  app.run(main)

공개 키 세트를 공유하는 일반적인 방법은 JWK 세트 형식입니다. 다음 예에서는 공개 키 세트를 JWK 세트 형식으로 변환하는 방법을 보여줍니다.

C++

// An example for converting a Tink keyset with public keys into a JWK set.
#include <iostream>
#include <memory>
#include <ostream>
#include <string>

#include "absl/flags/flag.h"
#include "absl/flags/parse.h"
#include "absl/log/check.h"
#include "util/util.h"
#include "tink/jwt/jwk_set_converter.h"
#include "tink/jwt/jwt_signature_config.h"
#include "tink/keyset_handle.h"
#include "tink/util/status.h"

ABSL_FLAG(std::string, public_keyset_filename, "",
          "Public keyset file in Tink's JSON format");
ABSL_FLAG(std::string, public_jwk_set_filename, "",
          "Path to the output public JWK set file");

namespace {

using ::crypto::tink::JwkSetFromPublicKeysetHandle;
using ::crypto::tink::KeysetHandle;
using ::crypto::tink::util::Status;
using ::crypto::tink::util::StatusOr;

void ValidateParams() {
  // ...
}

}  // namespace

namespace tink_cc_examples {

Status JwtGeneratePublicJwkSet(const std::string& public_keyset_filename,
                               const std::string& public_jwk_set_filename) {
  Status result = crypto::tink::JwtSignatureRegister();
  if (!result.ok()) return result;

  StatusOr<std::unique_ptr<KeysetHandle>> keyset_handle =
      ReadJsonCleartextKeyset(public_keyset_filename);
  if (!keyset_handle.ok()) return keyset_handle.status();

  StatusOr<std::string> public_jwk_set =
      JwkSetFromPublicKeysetHandle(**keyset_handle);
  if (!public_jwk_set.ok()) return keyset_handle.status();

  return WriteToFile(*public_jwk_set, public_jwk_set_filename);
}

}  // namespace tink_cc_examples

int main(int argc, char** argv) {
  absl::ParseCommandLine(argc, argv);

  ValidateParams();

  std::string public_keyset_filename =
      absl::GetFlag(FLAGS_public_keyset_filename);
  std::string public_jwk_set_filename =
      absl::GetFlag(FLAGS_public_jwk_set_filename);

  std::clog << "Convert public keyset in " << public_keyset_filename << " to ";
  std::clog << " to JWK set format; the result is written to "
            << public_jwk_set_filename << '\n';

  CHECK_OK(tink_cc_examples::JwtGeneratePublicJwkSet(public_keyset_filename,
                                                     public_jwk_set_filename));
  return 0;
}

Go

func Example_generateJWKS() {
	// A Tink keyset in JSON format with one JWT public key.
	publicJSONKeyset := `{
		"primaryKeyId": 1742360595,
		"key": [
			{
				"keyData": {
					"typeUrl": "type.googleapis.com/google.crypto.tink.JwtEcdsaPublicKey",
					"value": "EAEaIG40E1603MP9RyoomZth6r+NcO1XeODPZKMmP/lbD/kgIiBeoDMF9LS5BDCh6YgqE3DjHwWwnEKEI3WpPf8izEx1rQ==",
					"keyMaterialType": "ASYMMETRIC_PUBLIC"
				},
				"status": "ENABLED",
				"keyId": 1742360595,
				"outputPrefixType": "TINK"
			}
		]
	}`

	// Create a keyset handle from the keyset containing the public key. Because the
	// public keyset does not contain any secrets, we can use [keyset.ReadWithNoSecrets].
	publicKeysetHandle, err := keyset.ReadWithNoSecrets(
		keyset.NewJSONReader(bytes.NewBufferString(publicJSONKeyset)))
	if err != nil {
		log.Fatal(err)
	}

	// Create a publicJWKset from publicKeysetHandle.
	publicJWKset, err := jwt.JWKSetFromPublicKeysetHandle(publicKeysetHandle)
	if err != nil {
		log.Fatal(err)
	}

	// Remove whitespace so that we can compare it to the expected string.
	compactPublicJWKset := &bytes.Buffer{}
	err = json.Compact(compactPublicJWKset, publicJWKset)
	if err != nil {
		log.Fatal(err)
	}
	fmt.Println(compactPublicJWKset.String())
	// Output:
	// {"keys":[{"alg":"ES256","crv":"P-256","key_ops":["verify"],"kid":"Z9pQEw","kty":"EC","use":"sig","x":"bjQTXrTcw_1HKiiZm2Hqv41w7Vd44M9koyY_-VsP-SA","y":"XqAzBfS0uQQwoemIKhNw4x8FsJxChCN1qT3_IsxMda0"}]}
}

자바

package jwt;

import static java.nio.charset.StandardCharsets.UTF_8;

import com.google.crypto.tink.InsecureSecretKeyAccess;
import com.google.crypto.tink.KeysetHandle;
import com.google.crypto.tink.TinkJsonProtoKeysetFormat;
import com.google.crypto.tink.jwt.JwkSetConverter;
import com.google.crypto.tink.jwt.JwtSignatureConfig;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;

/**
 * A command-line example for generating the public JWT keyset in JWK set format.
 *
 * <p>It loads cleartext private keys from disk - this is not recommended!
 *
 * <p>It requires the following arguments:
 *
 * <ul>
 *   <li>private-keyset-file: Name of the input file containing the private keyset.
 *   <li>public-jwkset-file: Name of the output file containing the public key in JWK set format.
 */
public final class JwtGeneratePublicJwkSet {
  public static void main(String[] args) throws Exception {
    if (args.length != 2) {
      System.err.printf("Expected 2 parameters, got %d\n", args.length);
      System.err.println(
          "Usage: java JwtGeneratePublicJwkSet private-keyset-file public-jwk-set-file");
      System.exit(1);
    }

    Path privateKeysetFile = Paths.get(args[0]);
    Path publicJwkSetFile = Paths.get(args[1]);

    // Register all JWT signature key types with the Tink runtime.
    JwtSignatureConfig.register();

    // Read the keyset into a KeysetHandle.
    KeysetHandle privateKeysetHandle =
        TinkJsonProtoKeysetFormat.parseKeyset(
            new String(Files.readAllBytes(privateKeysetFile), UTF_8),
            InsecureSecretKeyAccess.get());

    // Export the public keyset as JWK set.
    String publicJwkSet =
        JwkSetConverter.fromPublicKeysetHandle(privateKeysetHandle.getPublicKeysetHandle());
    Files.write(publicJwkSetFile, publicJwkSet.getBytes(UTF_8));
  }

  private JwtGeneratePublicJwkSet() {}
}

Python

"""A utility for generating the public JWK set from the public keyset.
"""

from absl import app
from absl import flags
from absl import logging
import tink
from tink import jwt


_PUBLIC_KEYSET_PATH = flags.DEFINE_string(
    'public_keyset_path', None,
    'Path to the public keyset in Tink JSON format.')
_PUBLIC_JWK_SET_PATH = flags.DEFINE_string(
    'public_jwk_set_path', None, 'Path to public keyset in JWK format.')


def main(argv):
  del argv  # Unused.

  # Initialise Tink
  jwt.register_jwt_signature()

  # Read the keyset into a KeysetHandle
  with open(_PUBLIC_KEYSET_PATH.value, 'rt') as keyset_file:
    try:
      text = keyset_file.read()
      public_keyset_handle = tink.json_proto_keyset_format.parse_without_secret(
          text
      )
    except tink.TinkError as e:
      logging.exception('Error reading keyset: %s', e)
      return 1

  # Export Public Keyset as JWK set
  public_jwk_set = jwt.jwk_set_from_public_keyset_handle(public_keyset_handle)
  with open(_PUBLIC_JWK_SET_PATH.value, 'wt') as public_jwk_set_file:
    public_jwk_set_file.write(public_jwk_set)
  logging.info('The public JWK set has been written to %s',
               _PUBLIC_JWK_SET_PATH.value)


if __name__ == '__main__':
  flags.mark_flags_as_required(['public_keyset_path', 'public_jwk_set_path'])
  app.run(main)

JWT 확인

대부분의 사용 사례에서는 JWT_ES256 키 유형과 함께 JwtPublicKeyVerify 기본 요소를 사용하는 것이 좋습니다.

다음 예에서는 다른 당사자와 공개 키 세트를 공유하는 데 일반적으로 사용되는 형식인 JWK 세트 형식의 공개 키 세트를 사용하여 JWT를 확인하는 방법을 보여줍니다.

C++

// A utility for creating, signing and verifying JSON Web Tokens (JWT).
#include <iostream>
#include <memory>
#include <ostream>
#include <string>
#include <utility>

#include "absl/flags/flag.h"
#include "absl/flags/parse.h"
#include "absl/log/check.h"
#include "absl/strings/string_view.h"
#include "tink/config/global_registry.h"
#include "util/util.h"
#include "tink/jwt/jwk_set_converter.h"
#include "tink/jwt/jwt_public_key_verify.h"
#include "tink/jwt/jwt_signature_config.h"
#include "tink/jwt/jwt_validator.h"
#include "tink/keyset_handle.h"
#include "tink/util/status.h"

ABSL_FLAG(std::string, jwk_set_filename, "", "Path to the JWK set file");
ABSL_FLAG(std::string, audience, "", "Expected audience in the token");
ABSL_FLAG(std::string, token_filename, "", "Path to the token file");

namespace {

using ::crypto::tink::JwkSetToPublicKeysetHandle;
using ::crypto::tink::JwtPublicKeyVerify;
using ::crypto::tink::JwtValidator;
using ::crypto::tink::KeysetHandle;
using ::crypto::tink::util::Status;
using ::crypto::tink::util::StatusOr;

void ValidateParams() {
  // ...
}

}  // namespace

namespace tink_cc_examples {

// JWT verify example CLI implementation.
Status JwtVerify(const std::string& jwk_set_filename,
                 absl::string_view audience,
                 const std::string& token_filename) {
  Status result = crypto::tink::JwtSignatureRegister();
  if (!result.ok()) return result;

  // Read the JWK set from file and convert it.
  StatusOr<std::string> jwk_set = ReadFile(jwk_set_filename);
  if (!jwk_set.ok()) return jwk_set.status();
  StatusOr<std::unique_ptr<KeysetHandle>> keyset_handle =
      JwkSetToPublicKeysetHandle(*jwk_set);

  // Read the token.
  StatusOr<std::string> token = ReadFile(token_filename);
  if (!token.ok()) return token.status();

  StatusOr<JwtValidator> validator =
      crypto::tink::JwtValidatorBuilder().ExpectAudience(audience).Build();
  if (!validator.ok()) return validator.status();

  StatusOr<std::unique_ptr<JwtPublicKeyVerify>> jwt_verifier =
      (*keyset_handle)
          ->GetPrimitive<crypto::tink::JwtPublicKeyVerify>(
              crypto::tink::ConfigGlobalRegistry());
  if (!jwt_verifier.ok()) return jwt_verifier.status();

  return (*jwt_verifier)->VerifyAndDecode(*token, *validator).status();
}

}  // namespace tink_cc_examples

int main(int argc, char** argv) {
  absl::ParseCommandLine(argc, argv);

  ValidateParams();

  std::string jwk_set_filename = absl::GetFlag(FLAGS_jwk_set_filename);
  std::string audience = absl::GetFlag(FLAGS_audience);
  std::string token_filename = absl::GetFlag(FLAGS_token_filename);

  std::clog << "Using keyset in " << jwk_set_filename << " to ";
  std::clog << " verify a token with expected audience '" << audience << '\n';

  CHECK_OK(
      tink_cc_examples::JwtVerify(jwk_set_filename, audience, token_filename));
  return 0;
}

Go

func Example_verifyWithJWKS() {
	// A signed token with the subject 'example subject', audience 'example audience'.
	// and expiration on 2023-03-23.
	token := `eyJhbGciOiJFUzI1NiIsICJraWQiOiJaOXBRRXcifQ.eyJhdWQiOiJleGFtcGxlIGF1ZGllbmNlIiwgImV4cCI6MTY3OTUzMzIwMCwgInN1YiI6ImV4YW1wbGUgc3ViamVjdCJ9.ZvI0T84fJ1aouiB7n62kHOmbm0Hpfiz0JtYs15XVDT8KyoVYZ8hu_DGJUN47BqZIbuOI-rdu9TxJvutj8uF3Ow`

	// A public keyset in the JWK set format.
	publicJWKset := `{
		"keys":[
			{
				"alg":"ES256",
				"crv":"P-256",
				"key_ops":["verify"],
				"kid":"Z9pQEw",
				"kty":"EC",
				"use":"sig",
				"x":"bjQTXrTcw_1HKiiZm2Hqv41w7Vd44M9koyY_-VsP-SA",
				"y":"XqAzBfS0uQQwoemIKhNw4x8FsJxChCN1qT3_IsxMda0"
			}
		]
	}`

	// Create a keyset handle from publicJWKset.
	publicKeysetHandle, err := jwt.JWKSetToPublicKeysetHandle([]byte(publicJWKset))
	if err != nil {
		log.Fatal(err)
	}

	// Retrieve the Verifier primitive from publicKeysetHandle.
	verifier, err := jwt.NewVerifier(publicKeysetHandle)
	if err != nil {
		log.Fatal(err)
	}

	// Verify the signed token. For this example, we use a fixed date. Usually, you would
	// either not set FixedNow, or set it to the current time.
	audience := "example audience"
	validator, err := jwt.NewValidator(&jwt.ValidatorOpts{
		ExpectedAudience: &audience,
		FixedNow:         time.Date(2023, 3, 23, 0, 0, 0, 0, time.UTC),
	})
	if err != nil {
		log.Fatal(err)
	}
	verifiedJWT, err := verifier.VerifyAndDecode(token, validator)
	if err != nil {
		log.Fatal(err)
	}

	// Extract subject claim from the token.
	if !verifiedJWT.HasSubject() {
		log.Fatal(err)
	}
	extractedSubject, err := verifiedJWT.Subject()
	if err != nil {
		log.Fatal(err)
	}
	fmt.Println(extractedSubject)
	// Output: example subject
}

자바

package jwt;

import static java.nio.charset.StandardCharsets.UTF_8;

import com.google.crypto.tink.KeysetHandle;
import com.google.crypto.tink.RegistryConfiguration;
import com.google.crypto.tink.jwt.JwkSetConverter;
import com.google.crypto.tink.jwt.JwtPublicKeyVerify;
import com.google.crypto.tink.jwt.JwtSignatureConfig;
import com.google.crypto.tink.jwt.JwtValidator;
import com.google.crypto.tink.jwt.VerifiedJwt;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.time.Instant;
import java.time.temporal.ChronoUnit;
import java.util.List;

/**
 * A command-line utility for verifying JSON Web Tokens (JWTs).
 *
 * <p>It requires the following arguments:
 *
 * <ul>
 *   <li>public-jwkset-file: Name of the input file containing the public keyset in JWK set format.
 *   <li>audience: The audience claim to be used in the token
 *   <li>token-file: name of the input file containing the signed JWT.
 */
public final class JwtVerify {
  public static void main(String[] args) throws Exception {
    if (args.length != 3) {
      System.err.printf("Expected 3 parameters, got %d\n", args.length);
      System.err.println(
          "Usage: java JwtVerify public-jwk-set-file audience token-file");
      System.exit(1);
    }

    Path publicJwkSetFile = Paths.get(args[0]);
    String audience = args[1];
    Path tokenFile = Paths.get(args[2]);

    // Register all JWT signature key types with the Tink runtime.
    JwtSignatureConfig.register();

    // Read the public keyset in JWK set format into a KeysetHandle.
    KeysetHandle publicKeysetHandle =
        JwkSetConverter.toPublicKeysetHandle(
            new String(Files.readAllBytes(publicJwkSetFile), UTF_8));

    List<String> lines = Files.readAllLines(tokenFile, UTF_8);
    if (lines.size() != 1) {
      System.err.printf("The signature file should contain only one line,  got %d", lines.size());
      System.exit(1);
    }
    String signedToken = lines.get(0).trim();

    // Get the primitive.
    JwtPublicKeyVerify verifier =
        publicKeysetHandle.getPrimitive(RegistryConfiguration.get(), JwtPublicKeyVerify.class);

    // Use the primitive to verify a token.
    JwtValidator validator = JwtValidator.newBuilder().expectAudience(audience).build();
    VerifiedJwt verifiedJwt = verifier.verifyAndDecode(signedToken, validator);
    long seconds = ChronoUnit.SECONDS.between(Instant.now(), verifiedJwt.getExpiration());
    System.out.println("Token is valid and expires in " + seconds + " seconds.");
  }

  private JwtVerify() {}
}

Python

"""A utility for verifying Json Web Tokens (JWT)."""

import datetime

from absl import app
from absl import flags
from absl import logging
import tink
from tink import jwt


FLAGS = flags.FLAGS

_PUBLIC_JWK_SET_PATH = flags.DEFINE_string(
    'public_jwk_set_path', None, 'Path to public keyset in JWK set format.')
_AUDIENCE = flags.DEFINE_string('audience', None,
                                'Audience to be used in the token')
_TOKEN_PATH = flags.DEFINE_string('token_path', None,
                                  'Path to the signature file.')


def main(argv):
  del argv  # Unused.

  # Initialise Tink
  jwt.register_jwt_signature()

  with open(_PUBLIC_JWK_SET_PATH.value, 'rt') as public_jwk_set_file:
    try:
      text = public_jwk_set_file.read()
      keyset_handle = jwt.jwk_set_to_public_keyset_handle(text)
    except tink.TinkError as e:
      logging.exception('Error reading public JWK set: %s', e)
      return 1

  now = datetime.datetime.now(tz=datetime.timezone.utc)
  try:
    jwt_verify = keyset_handle.primitive(jwt.JwtPublicKeyVerify)
  except tink.TinkError as e:
    logging.exception('Error creating JwtPublicKeyVerify: %s', e)
    return 1

  # Verify token
  with open(_TOKEN_PATH.value, 'rt') as token_file:
    token = token_file.read()
  validator = jwt.new_validator(expected_audience=_AUDIENCE.value)
  try:
    verified_jwt = jwt_verify.verify_and_decode(token, validator)
    expires_in = verified_jwt.expiration() - now
    logging.info('Token is valid and expires in %s seconds', expires_in.seconds)
    return 0
  except tink.TinkError as e:
    logging.info('JWT verification failed: %s', e)
    return 1


if __name__ == '__main__':
  flags.mark_flags_as_required(['audience', 'token_path'])
  app.run(main)

JSON 웹 토큰(JWT)

Tink는 웹에서 널리 사용되는 표준인 JWT 생성을 지원합니다. Tink의 JWT 구현은 Tink 팀에서 사용하기에 안전하고 Tink 라이브러리에 적합하다고 간주하는 RFC 7519에 정의된 JWT 표준의 하위 집합을 제공합니다.

Tink는 거의 사용되지 않거나 올바르게 사용하기 어려운 표준 부분을 지원하지 않습니다. 제한사항은 다음과 같습니다.

  • Tink는 JWS 압축 직렬화 형식만 지원합니다. JWS JSON 직렬화JWE는 지원되지 않습니다.
  • Tink는 alg 헤더의 None 값을 지원하지 않습니다.
  • Tink는 typ, alg, kid 헤더만 지원합니다. 다른 모든 헤더는 지원되지 않습니다.
  • Tink는 서명이나 MAC이 확인되기 전에 토큰을 파싱하는 것을 허용하지 않습니다.

JWT 서명

토큰이 서로 다른 항목에 의해 생성되고 확인되는 경우 JwtPublicKeySignJwtPublicKeyVerify 기본 요소를 사용하여 비대칭 키를 사용해야 합니다. 비공개 키는 토큰을 생성하는 데 사용되고 공개 키는 토큰을 확인하는 데 사용됩니다. 이러한 기본 요소에서 지원하는 알고리즘ES256, ES384, ES512, RS256, RS384, RS512, PS256, PS384, PS512입니다.

키 유형 선택

JWT 서명은 Tink의 일반 디지털 서명과 다른 키 유형을 사용합니다. 일부 메타데이터 (예: algkid)는 키와 함께 저장해야 하므로 이 작업이 필요합니다.

대부분의 사용 사례에서는 JWT_ES256를 사용하는 것이 좋습니다. 이 키 유형으로 생성된 토큰에는 항상 kid 헤더가 있습니다. kid 헤더가 없는 약간 짧은 토큰을 선호하는 경우 키 유형 JWT_ES256_RAW을 선택합니다. 지원되는 모든 키 유형은 지원되는 키 유형을 참고하세요.

공개 키 세트 배포

Tink를 사용하면 공개 키 세트를 대부분의 JWT 라이브러리에서 이해하는 RFC 7517에 정의된 JWK 세트 형식으로 변환할 수 있습니다.

Tink는 다른 형식으로 공개 JWT 키를 내보내는 것을 지원하지 않습니다. 다른 형식에는 인증에 사용되는 algkid 메타데이터가 포함되어 있지 않기 때문에 오류가 발생하기 쉽고 키를 순환하기가 더 어려울 수 있습니다.

공개 키 세트를 한 번만 공유하는 것보다 공개 키 세트를 자동으로 업데이트하는 방법을 제공하는 것이 좋습니다. (그렇지 않으면 새 키로 순환하기가 매우 어렵습니다.) 이는 신뢰할 수 있고 보안이 유지되는 URL에 공개 키 세트를 게시하여 이루어지는 경우가 많습니다. 토큰을 확인하는 서버는 해당 URL에서 공개 키 세트를 주기적으로 다시 가져와야 합니다(예: 하루에 한 번). 키를 순환하려면 토큰에 서명하는 데 사용되기 하루 이상 전에 새 공개 키를 공개 키 세트에 추가해야 합니다. 그렇지 않으면 새 비공개 키로 서명된 새 토큰이 여전히 이전 공개 키 세트를 사용하는 서버에 의해 거부됩니다.

JWT MAC

Tink는 기본 요소 JwtMac를 사용하여 대칭 키가 있는 JWT도 지원합니다. 토큰이 동일한 항목에 의해 생성되고 확인되는 경우에만 이 기본 요소를 사용하세요. 이 기본 요소에서 지원하는 알고리즘HS256, HS384, HS512입니다.

키 유형 선택

JWT MAC 키 유형은 일반 MAC 키 유형과 다릅니다. 대부분의 사용 사례에서는 JWT_HS256를 사용하는 것이 좋습니다.