Library JWT Tink memungkinkan pembuatan dan verifikasi Token Web JSON (JWT).
Membuat JWT
Sebaiknya gunakan primitif JwtPublicKeySign dengan jenis kunci JWT_ES256 untuk sebagian besar kasus penggunaan.
Contoh berikut menunjukkan cara membuat JWT, dan cara mengonversi set kunci publik ke format set 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 }
Java
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)
Cara umum untuk membagikan set kunci publik adalah format set JWK. Contoh berikut menunjukkan cara mengonversi set kunci publik ke format set 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"}]} }
Java
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)
Memverifikasi JWT
Sebaiknya gunakan primitif JwtPublicKeyVerify dengan jenis kunci JWT_ES256 untuk sebagian besar kasus penggunaan.
Contoh berikut menunjukkan cara memverifikasi JWT menggunakan set kunci publik dalam format set JWK, yang merupakan format yang umum digunakan untuk membagikan set kunci publik kepada pihak lain.
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 }
Java
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)
Token Web JSON (JWT)
Tink mendukung pembuatan dan verifikasi JWT, yang merupakan standar yang banyak digunakan di web. Implementasi JWT Tink menyediakan subset standar JWT yang ditentukan dalam RFC 7519 yang dianggap aman untuk digunakan oleh tim Tink, dan yang cocok dengan library Tink.
Tink tidak mendukung bagian standar yang jarang digunakan atau sulit digunakan dengan benar. Berikut batasannya:
- Tink hanya mendukung format JWS Compact Serialization. Serialisasi JSON JWS dan JWE tidak didukung.
- Tink tidak mendukung nilai
None
di headeralg
. - Tink hanya mendukung header
typ
,alg
, dankid
. Header lainnya tidak didukung. - Tink tidak mengizinkan token diuraikan sebelum tanda tangan atau MAC diverifikasi.
Tanda Tangan JWT
Jika token dibuat dan diverifikasi oleh entitas yang berbeda, Anda harus menggunakan
kunci asimetris dengan primitif JwtPublicKeySign
dan JwtPublicKeyVerify
.
Kunci pribadi digunakan untuk membuat token, dan kunci publik digunakan untuk memverifikasi
token. Algoritma yang didukung oleh primitif ini adalah: ES256
,
ES384
, ES512
, RS256
, RS384
, RS512
, PS256
, PS384
, dan PS512
.
Pilih jenis kunci
Tanda tangan JWT menggunakan jenis kunci yang berbeda dengan tanda tangan digital normal di
Tink. Hal ini diperlukan karena beberapa metadata (seperti alg
dan kid
) perlu
disimpan dengan kunci.
Sebaiknya gunakan JWT_ES256
untuk sebagian besar kasus penggunaan. Token yang dibuat dengan jenis kunci ini selalu memiliki header kid
. Jika Anda lebih memilih token yang sedikit lebih pendek
tanpa header kid
, pilih jenis kunci JWT_ES256_RAW
. Untuk semua jenis kunci yang didukung, lihat Jenis Kunci yang Didukung.
Distribusi set kunci publik
Tink memungkinkan set kunci publik dikonversi ke dan dari format Set JWK yang ditentukan dalam RFC 7517, yang dipahami oleh sebagian besar library JWT.
Tink tidak mendukung ekspor kunci JWT publik dalam format lain. Alasannya
adalah karena format lain tidak berisi metadata alg
dan kid
yang akan
digunakan dalam verifikasi, sehingga penggunaannya lebih rentan terhadap error dan dapat
membuat rotasi kunci menjadi lebih sulit.
Sebaiknya jangan hanya membagikan set kunci publik satu kali, tetapi sediakan cara untuk memperbarui set kunci publik secara otomatis. (Jika tidak, mengganti kunci enkripsi dengan kunci baru akan sangat sulit.) Hal ini sering dilakukan dengan memublikasikan set kunci publik di URL yang tepercaya dan aman. Server yang memverifikasi token kemudian harus mengambil ulang set kunci publik secara berkala dari URL tersebut, misalnya sekali per hari. Untuk merotasi kunci, kunci publik baru harus ditambahkan ke set kunci publik setidaknya satu hari sebelum digunakan untuk menandatangani token. Jika tidak, token baru yang ditandatangani dengan kunci pribadi baru akan ditolak oleh server yang masih menggunakan keyset kunci publik lama.
MAC JWT
Tink juga mendukung JWT dengan kunci simetris dengan primitif JwtMac
. Gunakan
primitif ini hanya jika token dibuat dan diverifikasi oleh entitas yang sama. Algoritma yang didukung oleh primitif ini adalah HS256
, HS384
, dan HS512
.
Pilih jenis kunci
Jenis kunci MAC JWT berbeda dari jenis kunci MAC normal. Sebaiknya gunakan
JWT_HS256
untuk sebagian besar kasus penggunaan.