Skip to content

Latest commit

 

History

History
678 lines (578 loc) · 29.1 KB

interoperability.asciidoc

File metadata and controls

678 lines (578 loc) · 29.1 KB

Recommendations for Interoperability

The maximum utility of the MicroProfile JWT(MP-JWT) as a token format depends on the agreement between both identity providers and service providers. This means identity providers - responsible for issuing tokens - should be able to issue tokens using the MP-JWT format in a way that service providers can understand in order to introspect the token and gather information about a subject. To that end, the requirements for the MicroProfile JWT are:

  1. Be usable as an authentication token.

  2. Be usable as an authorization token that contains Jakarta EE application level roles indirectly granted via a groups claim.

  3. Can be mapped to IdentityStore in JSR375.

  4. Can support additional standard claims described in IANA JWT Assignments as well as non-standard claims.

To meet those requirements, we introduce 2 new claims to the MP-JWT:

  • "upn": A human readable claim that uniquely identifies the subject or user principal of the token, across the MicroProfile services the token will be accessed with.

  • "groups": A list of group names to be assigned to the principal of the MP-JWT. This typically will require a mapping at the application container level to application roles. A one-to-one mapping between group names and application role names is done by default when no custom mapping is defined in an implementation specific way.

Required MP-JWT Headers

alg

This JOSE header parameter identifies the cryptographic algorithm used to secure the JWT.

  • RSASSA-PKCS1-v1_5 SHA-256 or ECDSA using P-256 and SHA-256 algorithm are required when the claims have to be signed and must be specified as either "RS256", RFC7518, Section 3.3 or "ES256", RFC7515, Section 3.4.

  • Support for RSAES using Optimal Asymmetric Encryption Padding algorithm and RSAES OAEP using SHA-256 and MGF1 with SHA-256 is required when the claims or nested JWT tokens have to be encrypted. These algorithms are used for encrypting the content encryption key and must be specified as either RSA-OAEP or RSA-OAEP-256, RFC7518, Section 4.3. Support for the other key management key algorithms such as RSA-OAEP-384, RSA-OAEP-512 and others is optional.

enc

This JOSE header parameter is only required when the claims or nested JWT tokens have to be encrypted. It identifies the cryptographic algorithm used to encrypt the claims or nested JWT tokens. AES in Galois/Counter Mode (GCM) algorithm is required and must be specified as "A256GCM", RFC7518, Section 5.3

typ

This JOSE header parameter identifies the token as an RFC7519 and should be "JWT". RFC7519, Section 5.1

kid

This JOSE header parameter is a hint indicating which key was used to secure the JWT. RFC7515, Section-4.1.4. It may need to be set if the verification key is in the JWK format.

Required MP-JWT claims

iss

Identifies the token issuer

iat

Identifies the time at which the JWT was issued. This claim can be used to determine the age of the JWT. Its value MUST be a number containing a NumericDate value. RFC7519, Section 4.1.6

exp

Identifies the expiration time on or after which the JWT MUST NOT be accepted for processing. The processing of the "exp" claim requires that the current date/time MUST be before the expiration date/time listed in the "exp" claim. Implementers MAY provide for some small leeway, usually no more than a few minutes, to account for clock skew. Its value MUST be a number containing a NumericDate value. RFC7519, Section 4.1.4

upn (or preferred_username or sub)

MP-JWT "upn" claim is the user principal name in the java.security.Principal interface, and is the caller principal name in jakarta.security.enterprise.identitystore.IdentityStore. If this claim is missing, fallback to the "preferred_username", OIDC Section 5.1 should be attempted, and if that claim is missing, fallback to the "sub" claim should be used.

sub

Uniquely identifies the principal that is the subject of the JWT. See the "upn" claim for how this relates to the container java.security.Principal. RFC7519, Section 4.1.2.

jti

Provides a unique identifier for the JWT. The identifier value MUST be assigned in a manner that ensures that there is a negligible probability that the same value will be accidentally assigned to a different data object; if the application uses multiple issuers, collisions MUST be prevented among values produced by different issuers as well. The "jti" claim can be used to prevent the JWT from being replayed. The "jti" value is a case-sensitive string. RFC7519, Section 4.1.7

aud

Identifies the MP JWT endpoint(s) which can be accessed by JWT. RFC7519, Section 4.1.3

[NOTE] MP JWT implementations may enforce that JWT tokens contain all the recommended headers and claims. The recommended headers and claims may become required in the future versions of the MP JWT specification.

[NOTE] It is recommended that JWT tokens have a groups claim if the endpoint requires Authorization but MP JWT implementations can map the groups from the other claims if the tokens have been issued by OpenId Connect and other providers which currently do not support MP JWT.

If no groups information can be extracted directly from the groups claim or via the custom mappers from other custom claims in a given token then this token can be accepted if the endpoint requires Authentication only.

Note
NumericDate used by exp, iat, and other date related claims is a JSON numeric value representing the number of seconds from 1970-01-01T00:00:00Z UTC until the specified UTC date/time, ignoring leap seconds

An example minimal MP-JWT in JSON would be:

{
     "typ": "JWT",
    "alg": "RS256",
    "kid": "abc-1234567890"
}
{
       "iss": "https://server.example.com",
       "jti": "a-123",
       "exp": 1311281970,
       "iat": 1311280970,
       "sub": "24400320",
       "upn": "jdoe@server.example.com",
       "groups": ["red-group", "green-group", "admin-group", "admin"],
}

This specification defines a JsonWebToken java.security.Principal interface extension that makes this set of required claims available via get style accessors. The JsonWebToken interface definition is:

package org.eclipse.microprofile.jwt;
public interface JsonWebToken extends Principal {

    /**
     * Returns the unique name of this principal. This either comes from the upn
     * claim, or if that is missing, the preferred_username claim. Note that for
     * guaranteed interoperability a upn claim should be used.
     *
     * @return the unique name of this principal.
     */
    @Override
    String getName();

    /**
     * Get the raw bearer token string originally passed in the authentication
     * header
     * @return raw bear token string
     */
    default String getRawToken() {
        return getClaim(Claims.raw_token.name());
    }

    /**
     * The iss(Issuer) claim identifies the principal that issued the JWT
     * @return the iss claim.
     */
    default String getIssuer() {
        return getClaim(Claims.iss.name());
    }

    /**
     * The aud(Audience) claim identifies the recipients that the JWT is
     * intended for.
     * @return the aud claim.
     */
    default Set<String> getAudience() {
        return getClaim(Claims.aud.name());
    }

    /**
     * The sub(Subject) claim identifies the principal that is the subject of
     * the JWT. This is the token issuing IDP subject.
     *
     * @return the sub claim.
     */
    default String getSubject() {
        return getClaim(Claims.sub.name());
    }

    /**
     * The jti(JWT ID) claim provides a unique identifier for the JWT.
     The identifier value MUST be assigned in a manner that ensures that
     there is a negligible probability that the same value will be
     accidentally assigned to a different data object; if the application
     uses multiple issuers, collisions MUST be prevented among values
     produced by different issuers as well.  The "jti" claim can be used
     to prevent the JWT from being replayed.
     * @return the jti claim.
     */
    default String getTokenID() {
        return getClaim(Claims.jti.name());
    }

    /**
     * The exp (Expiration time) claim identifies the expiration time on or
     * after which the JWT MUST NOT be accepted
     * for processing in seconds since 1970-01-01T00:00:00Z UTC
     * @return the exp claim.
     */
    default long getExpirationTime() {
        return getClaim(Claims.exp.name());
    }

    /**
     * The iat(Issued at time) claim identifies the time at which the JWT was
     * issued in seconds since 1970-01-01T00:00:00Z UTC
     * @return the iat claim
     */
    default long getIssuedAtTime() {
        return getClaim(Claims.iat.name());
    }

    /**
     * The groups claim provides the group names the JWT principal has been
     * granted.
     *
     * This is a MicroProfile specific claim.
     * @return a possibly empty set of group names.
     */
    default Set<String> getGroups() {
        return getClaim(Claims.groups.name());
    }

    /**
     * Access the names of all claims are associated with this token.
     * @return non-standard claim names in the token
     */
    Set<String> getClaimNames();

    /**
     * Verify is a given claim exists
     * @param claimName - the name of the claim
     * @return true if the JsonWebToken contains the claim, false otherwise
     */
    default boolean containsClaim(String claimName) {
        return claim(claimName).isPresent();
    }

    /**
     * Access the value of the indicated claim.
     * @param claimName - the name of the claim
     * @return the value of the indicated claim if it exists, null otherwise.
     */
    <T> T getClaim(String claimName);

    /**
     * A utility method to access a claim value in an {@linkplain Optional}
     * wrapper
     * @param claimName - the name of the claim
     * @param <T> - the type of the claim value to return
     * @return an Optional wrapper of the claim value
     */
    default <T> Optional<T> claim(String claimName) {
        return Optional.ofNullable(getClaim(claimName));
    }

    /**
     * A utility method to access a claim value in an {@linkplain Optional}
     * wrapper
     * @param claim - the claim
     * @param <T> - the type of the claim value to return
     * @return an Optional wrapper of the claim value
     */
    default <T> Optional<T> claim(Claims claim) {
        return claim(claim.name());
    }
}

Additional Claims

The JWT can contain any number of other custom and standard claims, and these are made available from the JsonWebToken getClaim(String) method. An example MP-JWT that contains additional "auth_time", "preferred_username", "acr", "nbf", "aud" and "roles" claims is:

{
     "typ": "JWT",
    "alg": "RS256",
    "kid": "abc-1234567890"
}
{
   "iss": "https://server.example.com",
   "aud": ["s6BhdRkqt3"],
   "exp": 1311281970,
   "iat": 1311280970,
   "sub": "24400320",
   "upn": "jdoe@server.example.com",
   "groups: ["red-group", "green-group", "admin-group"],
   "roles": ["auditor", "administrator"],
   "jti": "a-123",
   "auth_time": 1311280969,
   "preferred_username": "jdoe",
   "acr": "phr",
   "nbf":  1311288970
}

The Claims Enumeration Utility Class, and the Set of Claim Value Types

The org.eclipse.microprofile.jwt.Claims utility class encapsulate an enumeration of all the standard JWT related claims along with a description and the required Java type for the claim as returned from the JsonWebToken#getClaim(String) method.

public enum Claims {
    // The base set of required claims that MUST have non-null values in the JsonWebToken
    iss("Issuer", String.class),
    sub("Subject", String.class),
    exp("Expiration Time", Long.class),
    iat("Issued At Time", Long.class),
    jti("JWT ID", String.class),
    upn("MP-JWT specific unique principal name", String.class),
    groups("MP-JWT specific groups permission grant", Set.class),
    raw_token("MP-JWT specific original bearer token", String.class),

    // The IANA registered, but MP-JWT optional claims
    aud("Audience", Set.class),
    nbf("Not Before", Long.class),
    auth_time("Time when the authentication occurred", Long.class),
    updated_at("Time the information was last updated", Long.class),
    azp("Authorized party - the party to which the ID Token was issued", String.class),
    nonce("Value used to associate a Client session with an ID Token", String.class),
    at_hash("Access Token hash value", Long.class),
    c_hash("Code hash value", Long.class),

    full_name("Full name", String.class),
    family_name("Surname(s) or last name(s)", String.class),
    middle_name("Middle name(s)", String.class),
    nickname("Casual name", String.class),
    given_name("Given name(s) or first name(s)", String.class),
    preferred_username("Shorthand name by which the End-User wishes to be referred to", String.class),
    email("Preferred e-mail address", String.class),
    email_verified("True if the e-mail address has been verified; otherwise false", Boolean.class),

    gender("Gender", String.class),
    birthdate("Birthday", String.class),
    zoneinfo("Time zone", String.class),
    locale("Locale", String.class),
    phone_number("Preferred telephone number", String.class),
    phone_number_verified("True if the phone number has been verified; otherwise false", Boolean.class),
    address("Preferred postal address", JsonObject.class),
    acr("Authentication Context Class Reference", String.class),
    amr("Authentication Methods References", String.class),
    sub_jwk("Public key used to check the signature of an ID Token", JsonObject.class),
    cnf("Confirmation", String.class),
    sip_from_tag("SIP From tag header field parameter value", String.class),
    sip_date("SIP Date header field value", String.class),
    sip_callid("SIP Call-Id header field value", String.class),
    sip_cseq_num("SIP CSeq numeric header field parameter value", String.class),
    sip_via_branch("SIP Via branch header field parameter value", String.class),
    orig("Originating Identity String", String.class),
    dest("Destination Identity String", String.class),
    mky("Media Key Fingerprint String", String.class),

    jwk("JSON Web Key Representing Public Key", JsonObject.class),
    jwe("Encrypted JSON Web Key", String.class),
    kid("Key identifier", String.class),
    jku("JWK Set URL", String.class),

    UNKNOWN("A catch all for any unknown claim", Object.class)
    ;
...
    /**
     * @return A desccription for the claim
     */
    public String getDescription() {
        return description;
    }

    /**
     * The required type of the claim
     * @return type of the claim
     */
    public Class<?> getType() {
        return type;
    }
}

Note that the groups and aud claims should only be injected into a Set of String, not as a comma delimited string.

The current complete set of valid claim types is therefore, (excluding the invalid Claims.UNKNOWN Void type):

  • java.lang.String

  • java.lang.Long and long

  • java.lang.Boolean and boolean

  • java.util.Set<java.lang.String>

  • jakarta.json.JsonValue.TRUE/FALSE

  • jakarta.json.JsonString

  • jakarta.json.JsonNumber

  • jakarta.json.JsonArray

  • jakarta.json.JsonObject

Custom claims not handled by the Claims enum must be any of the types defined in the previous acceptable claim types list.

Service Specific Authorization Claims

An extended form of authorization on a per service basis using a "resource_access" claim has been postponed to a future release. See Future Directions for more information.

Marking a JAX-RS Application as Requiring MP-JWT Access Control

Since the MicroProfile does not specify a deployment format, and currently does not rely on servlet metadata descriptors, we have added an org.eclipse.microprofile.auth.LoginConfig annotation that provides the same information as the web.xml login-config element. It’s intended usage is to mark a JAX-RS Application as requiring MicroProfile JWT RBAC as shown in the following sample:

import org.eclipse.microprofile.auth.LoginConfig;

import jakarta.ws.rs.ApplicationPath;
import jakarta.ws.rs.core.Application;

@LoginConfig(authMethod = "MP-JWT", realmName = "TCK-MP-JWT")
@ApplicationPath("/")
public class TCKApplication extends Application {
}

The MicroProfile JWT implementation is responsible for either directly processing this annotation, or mapping it to an equivalent form of metadata for the underlying implementation container.

Requirements for Rejecting MP-JWT Tokens

The MP-JWT specification requires that an MP-JWT implementation reject a JWT token as an invalid MP-JWT token if any of the following conditions are not met:

  1. The JWT must have a JOSE "alg" header that indicates the token was signed using the RS256 or ES256 algorithm when the service endpoint expects signed tokens.

  2. The JWT must have the JOSE "alg" and "enc" headers that indicate that the token was encrypted using the RSA-OAEP or RSA-OAEP-256 key management algorithms (or other key management algorithms supported by the implementation) and A256GCM content encryption algorithm when the service endpoint expects encrypted tokens. If the encrypted content is a nested signed JWT token then it must meet the condition 1.

  3. The JWT must have an "iss" claim representing the token issuer that maps to an MP-JWT implementation container runtime configured value. Any issuer other than those issuers that have been whitelisted by the container configuration must be rejected with an HTTP_UNAUTHENTICATED(401) error.

  4. The JWT must have an "iat" claim representing the token issuance time. It is impossible to calculate how long the token may have been used for without the "iat" claim so such tokens must be rejected with an HTTP_UNAUTHENTICATED(401) error.

  5. The JWT must have an "exp" claim representing the token expiration time. Tokens without the "exp" claim have an unlimited lifetime and must be rejected with an HTTP_UNAUTHENTICATED(401) error.

  6. The token must have at least one of "upn" or "preferred_username" or "sub" claim for Java Principal to have a name. Tokens from which no Principal name can be deduced must be rejected with an HTTP_UNAUTHENTICATED(401) error.

  7. The signed JWT token verification and encrypted JWT token decryption failures must lead to an HTTP_UNAUTHENTICATED(401) error.

Mapping MP-JWT Tokens to Jakarta EE Container APIs

The requirements of how a JWT should be exposed via the various Jakarta EE container APIs is discussed in this section. For the 1.0 release, the only mandatory container integration is with the JAX-RS container, and injection of the MP-JWT types.

CDI Injection Requirements

This section describes the requirements for MP-JWT implementations with regard to the injection of MP-JWT tokens and their associated claim values.

Injection of JsonWebToken

An MP-JWT implementation must support the injection of the currently authenticated caller as a JsonWebToken with @RequestScoped scoping which must work even if the outer bean is @ApplicationScoped:

@Path("/endp")
@DenyAll
@ApplicationScoped
public class RolesEndpoint {

    @Inject
    private JsonWebToken callerPrincipal;

If there is no JWT in the request, an empty JsonWebToken is injected, which means all method calls to this token return null. Note that MP JWT will still perform Authentication and Authorization if the endpoint requires these verifications. Effectively, the injected empty token is only visible on unauthenticated and unauthorized endpoints.

If a JWT is sent to an endpoint that does not require Authentication and/or Authorization then it still must be verified before it can be accessed via JsonWebToken interface.

Endpoints which need to control the authentication process themselves can check if a token is available by calling a JsonWebToken.getRawToken() method.

Injection of JsonWebToken claims via Raw Type, ClaimValue, jakarta.enterprise.inject.Instance and JSON-P Types

This specification requires support for injection of claims from the current JsonWebToken using the org.eclipse.microprofile.jwt.Claim qualifier:

/**
 * Annotation used to signify an injection point for a {@link ClaimValue} from
 * a {@link JsonWebToken}
 */
@Qualifier
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER, ElementType.TYPE})
public @interface Claim {
    /**
     * The value specifies the id name the claim to inject
     * @return the claim name
     * @see JsonWebToken#getClaim(String)
     */
    @Nonbinding
    String value() default "";

    /**
     * An alternate way of specifying a claim name using the {@linkplain Claims}
     * enum
     * @return the claim enum
     */
    @Nonbinding
    Claims standard() default Claims.UNKNOWN;
}

with @Dependent scoping.

MP-JWT implementations are required to throw a DeploymentException when detecting the ambiguous use of a @Claim qualifier that includes inconsistent non-default values for both the value and standard elements as is the case shown here:

@ApplicationScoped
public class MyEndpoint {
    @Inject
    @Claim(value="exp", standard=Claims.iat)
    private Long timeClaim;
...
}

The current complete set of valid claim types is:

  • java.lang.String

  • java.lang.Long and long

  • java.lang.Boolean and boolean

  • java.util.Set<java.lang.String>

  • jakarta.json.JsonValue.TRUE/FALSE

  • jakarta.json.JsonString

  • jakarta.json.JsonNumber

  • jakarta.json.JsonArray

  • jakarta.json.JsonObject

  • java.util.Optional wrapper of the above types.

  • org.eclipse.microprofile.jwt.ClaimValue wrapper of the above types.

MP-JWT implementations are required to support injection of the claim values using any of these types. The claims are automatically converted to the type used in the injection point where the type must be any of the types defined in the previous acceptable claim types list.

The org.eclipse.microprofile.jwt.ClaimValue interface is:

/**
 * A representation of a claim in a {@link JsonWebToken}
 * @param <T> the expected type of the claim
 */
public interface ClaimValue<T> extends Principal {

    /**
     * Access the name of the claim.
     * @return The name of the claim as seen in the JsonWebToken content
     */
    @Override
    public String getName();

    /**
     * Access the value of the claim.
     * @return the value of the claim.
     */
    public T getValue();
}

The following example code fragment illustrates various examples of injecting different types of claims using a range of generic forms of the ClaimValue, JsonValue as well as the raw claim types:

import org.eclipse.microprofile.jwt.Claim;
import org.eclipse.microprofile.jwt.ClaimValue;
import org.eclipse.microprofile.jwt.Claims;

@Path("/endp")
@DenyAll
@RequestScoped
public class RolesEndpoint {
...

    // Raw types
    @Inject
    @Claim(standard = Claims.raw_token)
    private String rawToken;
    @Inject // (1)
    @Claim(standard=Claims.iat)
    private Long issuedAt;

    // ClaimValue wrappers
    @Inject // (2)
    @Claim(standard = Claims.raw_token)
    private ClaimValue<String> rawTokenCV;
    @Inject
    @Claim(standard = Claims.iss)
    private ClaimValue<String> issuer;
    @Inject
    @Claim(standard = Claims.jti)
    private ClaimValue<String> jti;
    @Inject // (3)
    @Claim("jti")
    private ClaimValue<Optional<String>> optJTI;
    @Inject
    @Claim("jti")
    private ClaimValue objJTI;
    @Inject
    @Claim("groups")
    private ClaimValue<Set<String>> groups;
    @Inject // (4)
    @Claim(standard=Claims.iat)
    private ClaimValue<Long> issuedAtCV;
    @Inject
    @Claim("iat")
    private ClaimValue<Long> dupIssuedAt;
    @Inject
    @Claim("sub")
    private ClaimValue<Optional<String>> optSubject;
    @Inject
    @Claim("auth_time")
    private ClaimValue<Optional<Long>> authTime;
    @Inject // (5)
    @Claim("custom-missing")
    private ClaimValue<Optional<Long>> custom;
    //
    @Inject
    @Claim(standard = Claims.jti)
    private Instance<String> providerJTI;
    @Inject // (6)
    @Claim(standard = Claims.iat)
    private Instance<Long> providerIAT;
    @Inject
    @Claim("groups")
    private Instance<Set<String>> providerGroups;
    //
    @Inject
    @Claim(standard = Claims.jti)
    private JsonString jsonJTI;
    @Inject
    @Claim(standard = Claims.iat)
    private JsonNumber jsonIAT;
    @Inject // (7)
    @Claim("roles")
    private JsonArray jsonRoles;
    @Inject
    @Claim("customObject")
    private JsonObject jsonCustomObject;
  1. Injection of a non-proxyable raw type like java.lang.Long must happen in a RequestScoped bean as the producer will have dependendent scope.

  2. Injection of the raw MP-JWT token string.

  3. Injection of the jti token id as an Optional<String> wrapper.

  4. Injection of the issued at time claim using an @Claim that references the claim name using the Claims.iat enum value.

  5. Injection of a custom claim that does exist will result in an Optional<Long> value for which isPresent() will return false.

  6. Another injection of a non-proxyable raw type like java.lang.Long, but the use of the jakarta.enterprise.inject.Instance interface allows for injection to occur in non-RequestScoped contexts.

  7. Injection of a JsonArray of role names via a custom "roles" claim.

The example shows that one may specify the name of the claim using a string or a Claims enum value. The string form would allow for specifying non-standard claims while the Claims enum approach guards against typos.

Handling of Non-RequestScoped Injection of Claim Values

MP-JWT implementations are required to support a claim value injection into @ApplicationScoped scoped beans. A warning may be issued when the injection point is not an org.eclipse.microprofile.jwt.ClaimValue or jakarta.inject.Provider interface.

MP-JWT implementations are required to generate a jakarta.enterprise.inject.spi.DeploymentException for a claim value injection into Passivation capable beans, for example, @SessionScoped.

MP JWT implementations may issue a warning for any other context with a lifetime greater than @RequestScoped.

Note
If one needs to inject a claim value into a scope with a lifetime greater than @RequestScoped, such as @ApplicationScoped or @SessionScoped, one can also use the jakarta.enterprise.inject.Instance interface to do so.

JAX-RS Container API Integration

The behavior of the following JAX-RS security related methods is required for MP-JWT implementations.

jakarta.ws.rs.core.SecurityContext.getUserPrincipal()

The java.security.Principal returned from these methods MUST be an instance of org.eclipse.microprofile.jwt.JsonWebToken.

jakarta.ws.rs.core.SecurityContext#isUserInRole(String)

This method MUST return true for any name that is included in the MP-JWT "groups" claim, as well as for any role name that has been mapped to a group name in the MP-JWT "groups" claim.

Using the Common Security Annotations for the Java Platform (JSR-250)

The expectations for use of the various security annotations described in sections 2.9 - 2.12 of JSR-250 (@RolesAllowed, @PermitAll, @DenyAll), is that MP-JWT containers support the behavior as described in those sections. In particular, the interaction between the annotations should be as described in section 2.12 of JSR-250.

Mapping the @RolesAllowed to the MP-JWT group claim

In terms of mapping between the MP-JWT claims and role names used in @RolesAllowed, the role names that have been mapped to group names in the MP-JWT "groups" claim, MUST result in an allowing authorization decision wherever the security constraint has been applied.

Mapping MP-JWT Token to Other Container APIs

For non-Jakarta EE containers that provide access to some form of java.security.Principal representation of an authenticated caller, the caller principal MUST be compatible with the org.eclipse.microprofile.jwt.JsonWebToken interface.