Verification of JSon Web Tokens (JWT) passed to the Microservice in HTTP requests at runtime is done with the RSA Public Key corresponding to the RSA Private Key held by the JWT Issuer.
At the time of JWT creation, the Issuer will sign the JWT with its Private Key before passing it to the user. Upon receiving the JWT in future HTTP requests, Microservices can then use the matching Public Key to verify the JWT and trust the user information (claims) it contains.
The goal of this chapter is to detail means of passing the Public Key from the JWT Issuer to the MicroProfile JWT implementation as well as any standard configuration options for the verification itself.
MicroProfile JWT leverages the MicroProfile Config specification to provide a consistent means of passing all supported configuration options. Prior to MicroProfile JWT 1.1 all configuration options for the Public Key and verification were vendor-specific. Any vendor-specific methods of configuration are still valid and shall be considered to override any standard configuration mechanisms.
In practice, the Public Key is often obtained manually from the JWT Issuer and stored in or passed to the binary of the Microservice. If your public Keys do not rotate frequently, then storing them in the binary image or on disk is a realistic option for many environments. For reference, SSL/TLS Certificates to support HTTPS, which are also Public Key based, are usually configured in the JVM itself and last for up to two years.
Alternatively, Public Keys may be obtained by the Microservice at runtime, directly from the JWT Issuer via HTTPS request. MicroProfile JWT implementations are required to support this method of fetching the Public Key from the JWT Issuer via means defined here. It should be noted, however, not all JWT Issuers support downloading of the Public Key via HTTPS request.
Support for RSA Public Keys of 1024 or 2048 bits in length is required. Other key sizes are allowed, but should be considered vendor-specific.
Other asymmetric signature algorithms are allowed, but should be considered vendor-specific. This includes Digital Signature Algorithm (DSA), Diffie-Hellman (DS), Elliptical curve Digital Signature Algorithm (ECDSA), Edwards-curve Digital Signature Algorithm (EdDSA, aka ed25519)
Note
|
Symmetrically signed JWTs such as HMAC-SHA256 (hs256) are explicitly not supported, deemed insecure for a distributed Microservice architecture where JWTs are expected to be passed around freely. Use of symmetric signatures would require all microservices to share a secret, eliminating the ability to determine who created the JWT. |
Public Keys may be formatted in any of the following formats, specified in order of precedence:
-
Public Key Cryptography Standards #8 (PKCS#8) PEM
-
JSON Web Key (JWK)
-
JSON Web Key Set (JWKS)
-
JSON Web Key (JWK) Base64 URL encoded
-
JSON Web Key Set (JWKS) Base64 URL encoded
Attempts to parse the Public Key text will proceed in the order specified above until a valid Public Key can be derived.
Support for other Public Key formats such as PKCS#1, SSH2, or OpenSSH Public Key format is considered optional.
MicroProfile JWT implementations are required to throw a DeploymentException
when given
a public key that cannot be parsed using either the standardly supported or
vendor-specific key formats.
MicroProfile JWT implementations are required to throw a DeploymentException
when given
a Private Key in any format.
Public Key Cryptography Standards #8 (PKCS#8) PEM format is a plain text format and is the default format for OpenSSL, many public/private key tools and is natively supported in Java.
The format consists of a Base64 URL encoded value wrapped in a standard -----BEGIN PUBLIC
KEY-----
header and footer. The Base64 URL encoded data can be decoded and the resulting
byte array passed directly to java.security.spec.PKCS8EncodedKeySpec
.
The following is an example of a valid RSA 2048 bit Public Key in PKCS#8 PEM format.
-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA0440JtmhlywtkMvR6tTM
s0U6e9Ja4xXj5+q+joWdT2xCHt91Ck9+5C5WOaRTco4CPFMBxoUPi1jktW5c+Oyk
nOIACXu6grXexarFQLjsREE+dkDVrMu75f7Gb9/lC7mrVM73118wnMP2u5MOQIoX
OqqC1y1gaoJaLp/OjTiJGCm4uxzubzUPN5IDAFaTfK+QErhtcGeBDwWjvikGfUfX
+WVq74DOoggLiGbB4jsT8iVXEm53JcoEY8nVr2ygr92TuU1+xLAGisjRSYJVe7V1
tpdRG1CiyCIkqhDFfFBGhFnWlu4gKMiT0KToA9GJfOuCz67XZEAhQYizcXbn1uxa
OQIDAQAB
-----END PUBLIC KEY-----
MicroProfile JWT implementations must inspect the supplied Public Key body for the
-----BEGIN PUBLIC KEY-----
header and parse the key as PCKS#8 if found.
Support for the legacy PKCS#1 format is not required and should be considered vendor-specific.
PKCS#1 formatted keys can be identified by the use of the -----BEGIN RSA PUBLIC KEY-----
header and footer.
MicroProfile JWT implementations are required to throw a DeploymentException
if given a
Private Key in any format such as -----BEGIN PRIVATE KEY-----
or -----BEGIN RSA PRIVATE
KEY-----
JSON Web Key (JWK) allows for a Public Key to be formatted in json and optionally Base64 encoded.
At minimum JWK formatted Public Keys must contain the kty
field. RSA Public Keys must
contain the n
and e
fields.
The following example is the previously shown PKCS#8 PEM formatted Public Key converted to JWK format.
{
"kty": "RSA",
"n": "sszbq1NfZap2IceUCO9rCF9ZYfHE3oU5m6Avgyxu1LmlB6rNPejO-eB7T9iIhxXCEKsGDcx4Cpo5nxnW5PSQZM_wzXg1bAOZ3O6k57EoFC108cB0hdvOiCXXKOZGrGiZuF7q5Zt1ftqIk7oK2gbItSdB7dDrR4CSJSGhsSu5mP0",
"e": "AQAB"
}
MicroProfile JWT implementations are required to throw a DeploymentException
if the JWK
kty
field is missing or JSON text is found, but does not follow either JWK or JWKS
format.
The JWK may be supplied in plain JSON or Base64 URL encoded JSON format.
See RFC-7517 for further details on JWK format and optional fields.
The JSON Web Key Set (JWKS) format allows for multiple keys to supplied, which can be useful for either key rotation or supporting environments that have multiple JWT Issuers and therefore multiple Public Keys.
An example of a valid JWKS:
{
"keys": [
{
"kid": "orange-1234",
"kty": "RSA",
"n": "sszbq1NfZap2IceUCO9rCF9ZYfHE3oU5m6Avgyxu1LmlB6rNPejO-eB7T9iIhxXCEKsGDcx4Cpo5nxnW5PSQZM_wzXg1bAOZ3O6k57EoFC108cB0hdvOiCXXKOZGrGiZuF7q5Zt1ftqIk7oK2gbItSdB7dDrR4CSJSGhsSu5mP0",
"e": "AQAB"
},
{
"kid": "orange-5678",
"kty": "RSA",
"n": "xC7RfPpTo7362rzATBu45Jv0updEZcr3IqymjbZRkpgTR8B19b_rS4dIficnyyU0plefkE2nJJyJbeW3Fon9BLe4_srfXtqiBKcyqINeg0GrzIqoztZBmmmdo13lELSrGP91oHL-UtCd1u5C1HoJc4bLpjUYxqOrJI4mmRC3Ksk5DV2OS1L5P4nBWIcR1oi6RQaFXy3zam3j1TbCD5urkE1CfUATFwfXfFSPTGo7shNqsgaWgy6B205l5Lq5UmMUBG0prK79ymjJemODwrB445z-lk3CTtlMN7bcQ3nC8xh-Mb2XmRB0uoU4K3kHTsofXG4dUHWJ8wGXEXgJNOPzOQ",
"e": "AQAB"
}
]
}
If the incoming JWT uses the kid
header field and there is a key in the supplied JWK set
with the same kid
, only that key is considered for verification of the JWT’s digital
signature.
For example, the following decoded JWT would involve a check on only the orange-5678
key.
{
"alg": "RS256",
"typ": "JWT",
"kid": "orange-5678"
}.
{
"sub": "1234567890",
"name": "John Doe",
"admin": true,
"iat": 1516239022
}
The JWKS may be supplied in plain JSON or Base64 URL encoded JSON format.
Parameters are passed using the MicroProfile Config specification. This specification allows at minimum configuration options to be specified in the microservice binary itself or via command-line via -D properties as follows:
java -jar movieservice.jar -Dmp.jwt.verify.publickey.location=orange.pem
By convention of the MicroProfile JWT specification, property names are always lowercase
and begin with mp.jwt.
The mp.jwt.verify.publickey
config property allows the Public Key text itself to be
supplied as a string. The Public Key will be parsed from the supplied string in the order
defined in section Supported Public Key Formats.
The following example shows a Base 64 URL encoded JWK passed via system property.
java -jar movieservice.jar -Dmp.jwt.verify.publickey=eyJrdHkiOiJSU0EiLCJuI\
joieEM3UmZQcFRvNzM2MnJ6QVRCdTQ1SnYwdXBkRVpjcjNJcXltamJaUmtwZ1RSOEIxOWJfclM\
0ZElmaWNueXlVMHBsZWZrRTJuSkp5SmJlVzNGb245QkxlNF9zcmZYdHFpQktjeXFJTmVnMEdye\
klxb3p0WkJtbW1kbzEzbEVMU3JHUDkxb0hMLVV0Q2QxdTVDMUhvSmM0YkxwalVZeHFPckpJNG1\
tUkMzS3NrNURWMk9TMUw1UDRuQldJY1Ixb2k2UlFhRlh5M3phbTNqMVRiQ0Q1dXJrRTFDZlVBV\
EZ3ZlhmRlNQVEdvN3NoTnFzZ2FXZ3k2QjIwNWw1THE1VW1NVUJHMHBySzc5eW1qSmVtT0R3ckI\
0NDV6LWxrM0NUdGxNTjdiY1EzbkM4eGgtTWIyWG1SQjB1b1U0SzNrSFRzb2ZYRzRkVUhXSjh3R\
1hFWGdKTk9Qek9RIiwiZSI6IkFRQUIifQo
When supplied, mp.jwt.verify.publickey
will override other standard means to supply the
Public Key such as mp.jwt.verify.publickey.location
. Vendor-specific options for
supplying the key will always take precedence.
If neither the mp.jwt.verify.publickey
nor mp.jwt.verify.publickey.location
are supplied configuration are supplied, the MP-JWT signer configuration will
default to a vendor specific behavior as was the case for MP-JWT 1.0.
MicroProfile JWT implementations are required to throw a DeploymentException
if both
mp.jwt.verify.publickey
and mp.jwt.verify.publickey.location
are supplied.
The mp.jwt.verify.publickey
config property allows for an external or internal location
of Public Key to be specified. The value may be a relative path or a URL.
MicroProfile JWT implementations are required to check the path at startup or deploy time. Reloading the Public Key from the location at runtime as well as the frequency of any such reloading is beyond the scope of this specification and any such feature should be considered vendor-specific.
Relative or non-URL paths supplied as the location are resolved in the following order:
-
new File(location)
-
Thread.currentThread().getContextClassLoader().getResource(location)
The following example shows the file orange.pem
supplied as either a file in the
Microservice’s binary or locally on disk.
java -jar movieservice.jar -Dmp.jwt.verify.publickey.location=orange.pem
Any non-URL is treated identically and may be a path inside or outside the archive.
java -jar movieservice.jar -Dmp.jwt.verify.publickey.location=/META-INF/orange.pem
Parsing of the file contents occurs as defined in Supported Public Key Formats
File URL paths supplied as the location allow for explicit externalization of the file via full url.
java -jar movieservice.jar -Dmp.jwt.verify.publickey.location=file:///opt/keys/orange.pem
Parsing of the file contents occurs as defined in Supported Public Key Formats
HTTP and HTTPS URL paths allow for the Public Key to be fetched from a remote host, which may be the JWT Issuer or some other trusted internet or intranet location.
The location supplied must respond to an HTTP GET. Parsing of the HTTP message body occurs as defined in Supported Public Key Formats
java -jar movieservice.jar -Dmp.jwt.verify.publickey.location=https://location.dev/widget/issuer
Other forms of HTTP requests and responses may be supported, but should be considered vendor-specific.
All other locations containing a colon will be considered as URLs and be resolved using the following method:
-
new URL(location).openStream()
Thus additional vendor-specific or user-defined options can easily be added.
Example custom "smb:" location
java -jar movieservice.jar -Dmp.jwt.verify.publickey.location=smb://Host/orange.pem -Djava.protocol.handler.pkgs=org.foo
Example stub for custom "smb:" URL Handler
package org.foo.smb;
import java.io.IOException;
import java.net.URL;
import java.net.URLConnection;
import java.net.URLStreamHandler;
/**
* The smb: URL protocol handler
*/
public class Handler extends URLStreamHandler {
@Override
protected URLConnection openConnection(URL u) throws IOException {
return // your URLConnection implementation
}
}
See java.net.URL javadoc for more details.
Parsing of the InputStream
occurs as defined in Supported Public Key Formats and must
return Public Key text in one of the supported formats.