ไลบรารี JWT ของ Tink ช่วยให้สร้างและยืนยันโทเค็นเว็บ JSON (JWT) ได้
สร้าง JWT
เราขอแนะนำให้ใช้ Primitive JwtPublicKeySign ที่มีคีย์ประเภท JWT_ES256 สำหรับ Use Case ส่วนใหญ่
ตัวอย่างต่อไปนี้แสดงวิธีสร้าง 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 }
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)
รูปแบบชุด 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"}]} }
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)
ยืนยัน JWT
เราขอแนะนำให้ใช้ Primitive JwtPublicKeyVerify ที่มีคีย์ประเภท JWT_ES256 สำหรับกรณีการใช้งานส่วนใหญ่
ตัวอย่างต่อไปนี้แสดงวิธีตรวจสอบ JWT โดยใช้ชุดคีย์สาธารณะในรูปแบบ JWK set ซึ่งเป็นรูปแบบที่ใช้กันโดยทั่วไปในการแชร์ชุดคีย์สาธารณะกับ บุคคลที่สาม
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)
โทเค็นเว็บ JSON (JWT)
Tink รองรับการสร้างและยืนยัน JWT ซึ่งเป็นมาตรฐานที่ใช้กันอย่างแพร่หลายบนเว็บ การใช้งาน JWT ของ Tink มีชุดย่อยของมาตรฐาน JWT ที่กำหนดไว้ใน RFC 7519 ซึ่งทีม Tink เห็นว่าปลอดภัยที่จะใช้และ เหมาะกับไลบรารี Tink
Tink ไม่รองรับส่วนของมาตรฐานที่แทบไม่ได้ใช้หรือใช้งานอย่างถูกต้องได้ยาก ข้อจำกัดมีดังนี้
- Tink รองรับเฉพาะรูปแบบ JWS Compact Serialization ไม่รองรับ JWS JSON Serialization และ JWE
- Tink ไม่รองรับค่า
None
ในส่วนหัวalg
- Tink รองรับเฉพาะส่วนหัว
typ
,alg
และkid
ระบบไม่รองรับส่วนหัวอื่นๆ ทั้งหมด - Tink ไม่อนุญาตให้แยกวิเคราะห์โทเค็นก่อนที่จะยืนยันลายเซ็นหรือ MAC
ลายเซ็น JWT
หากโทเค็นสร้างและยืนยันโดยเอนทิตีที่แตกต่างกัน คุณควรใช้
คีย์แบบอสมมาตรกับ Primitive JwtPublicKeySign
และ JwtPublicKeyVerify
ระบบจะใช้คีย์ส่วนตัวเพื่อสร้างโทเค็น และใช้คีย์สาธารณะเพื่อยืนยันโทเค็น
อัลกอริทึมที่รองรับโดยองค์ประกอบพื้นฐานเหล่านี้ ได้แก่ ES256
,
ES384
, ES512
, RS256
, RS384
, RS512
, PS256
, PS384
และ PS512
เลือกประเภทคีย์
ลายเซ็น JWT ใช้คีย์ประเภทอื่นที่ไม่ใช่ลายเซ็นดิจิทัลปกติใน
Tink เนื่องจากข้อมูลเมตาบางรายการ (เช่น alg
และ kid
) ต้องจัดเก็บไว้กับคีย์
เราขอแนะนำให้ใช้ JWT_ES256
สำหรับกรณีการใช้งานส่วนใหญ่ โทเค็นที่สร้างด้วยคีย์ประเภทนี้จะมีส่วนหัว kid
เสมอ หากต้องการโทเค็นที่สั้นลงเล็กน้อย
โดยไม่มีส่วนหัว kid
ให้เลือกประเภทคีย์ JWT_ES256_RAW
ดูคีย์ประเภททั้งหมดที่รองรับได้ที่ประเภทคีย์ที่รองรับ
การกระจายชุดคีย์สาธารณะ
Tink อนุญาตให้แปลงชุดคีย์สาธารณะเป็นและจากรูปแบบชุด JWK ที่กำหนดไว้ใน RFC 7517 ซึ่งไลบรารี JWT ส่วนใหญ่เข้าใจ
Tink ไม่รองรับการส่งออกคีย์สาธารณะของ JWT ในรูปแบบอื่นๆ สาเหตุ
คือรูปแบบอื่นๆ ไม่มีข้อมูลเมตา alg
และ kid
ที่จะ
ใช้ในการยืนยัน ซึ่งทำให้การใช้รูปแบบเหล่านั้นมีโอกาสเกิดข้อผิดพลาดมากขึ้นและอาจทำให้
การหมุนเวียนคีย์ทำได้ยากขึ้น
เราขอแนะนำให้คุณไม่เพียงแชร์ชุดคีย์สาธารณะเพียงครั้งเดียว แต่ควรมีวิธีอัปเดตชุดคีย์สาธารณะโดยอัตโนมัติ (หากไม่ทำ การเปลี่ยนไปใช้คีย์ใหม่จะทำได้ยากมาก) โดยมักจะทำด้วยการเผยแพร่ชุดคีย์สาธารณะใน URL ที่เชื่อถือได้และปลอดภัย จากนั้นเซิร์ฟเวอร์ที่ยืนยันโทเค็นจะต้องดึงข้อมูลชุดคีย์สาธารณะจาก URL นั้นเป็นระยะๆ เช่น วันละครั้ง หากต้องการหมุนเวียนคีย์ คุณต้องเพิ่มคีย์สาธารณะใหม่ลงในชุดคีย์สาธารณะอย่างน้อย 1 วันก่อน ที่จะใช้ลงนามในโทเค็น ไม่เช่นนั้น เซิร์ฟเวอร์ที่ยังใช้ชุดคีย์สาธารณะเดิมจะปฏิเสธโทเค็นใหม่ที่ลงนามด้วยคีย์ส่วนตัวใหม่
JWT MAC
นอกจากนี้ Tink ยังรองรับ JWT ที่มีคีย์แบบสมมาตรด้วย Primitive JwtMac
ใช้
Primitive นี้เฉพาะในกรณีที่เอนทิตีเดียวกันสร้างและยืนยันโทเค็น อัลกอริทึมที่รองรับโดย Primitive นี้คือ HS256
, HS384
และ HS512
เลือกประเภทคีย์
ประเภทคีย์ JWT MAC จะแตกต่างจากประเภทคีย์ MAC ปกติ เราขอแนะนำให้ใช้
JWT_HS256
สำหรับกรณีการใช้งานส่วนใหญ่