001package com.nimbusds.openid.connect.provider.spi.tokens; 002 003 004import java.util.*; 005 006import net.jcip.annotations.ThreadSafe; 007import net.minidev.json.JSONObject; 008 009import com.nimbusds.jose.JOSEObjectType; 010import com.nimbusds.jwt.JWTClaimsSet; 011import com.nimbusds.oauth2.sdk.auth.X509CertificateConfirmation; 012import com.nimbusds.oauth2.sdk.id.*; 013 014 015/** 016 * Base implementation of the SPI for encoding and decoding authorisations for 017 * self-contained access tokens into JWT claims sets. 018 * 019 * <p>Provides encoding and decoding for all token parameters for which there 020 * is an appropriate standard JWT claim (see JSON Web Token (JWT) (RFC 7519), 021 * section 4.1, OAuth 2.0 Mutual-TLS Client Authentication and 022 * Certificate-Bound Access Tokens (RFC 8705), section 3.1): 023 * 024 * <ul> 025 * <li>{@link AccessTokenAuthorization#getSubject subject} - "sub" 026 * <li>{@link AccessTokenAuthorization#getActor actor} - "act" 027 * <li>{@link AccessTokenAuthorization#getExpirationTime expiration time} - "exp" 028 * <li>{@link AccessTokenAuthorization#getIssueTime issue time} - "iat" 029 * <li>{@link AccessTokenAuthorization#getIssuer issuer} - "iss" 030 * <li>{@link AccessTokenAuthorization#getAudienceList audience} - "aud" 031 * <li>{@link AccessTokenAuthorization#getJWTID JWT ID} - "jti" 032 * <li>{@link AccessTokenAuthorization#getClientCertificateConfirmation client X.509 certificate SHA-256 thumbprint (mTLS)} - "cnf.x5t#S256" 033 * </ul> 034 * 035 * <p>The extending class should implement encoding and decoding for the 036 * remaining token parameters: 037 * 038 * <ul> 039 * <li>{@link AccessTokenAuthorization#getClientID client ID} 040 * <li>{@link AccessTokenAuthorization#getScope scope} 041 * <li>{@link AccessTokenAuthorization#getClaimNames consented OpenID claim names} 042 * <li>{@link AccessTokenAuthorization#getClaimsLocales preferred claims locales} 043 * <li>{@link AccessTokenAuthorization#getPresetClaims preset OpenID claims} 044 * <li>{@link AccessTokenAuthorization#getClaimsData claims fulfillment data} 045 * <li>{@link AccessTokenAuthorization#getData additional data} 046 * <li>{@link AccessTokenAuthorization#getOtherTopLevelParameters other top-level parameters} 047 * </ul> 048 */ 049@ThreadSafe 050public abstract class BaseSelfContainedAccessTokenClaimsCodec implements SelfContainedAccessTokenClaimsCodec { 051 052 053 /** 054 * The supported claim names. 055 */ 056 public static final Set<String> SUPPORTED_CLAIM_NAMES = Collections.unmodifiableSet( 057 new HashSet<>(Arrays.asList( 058 "sub", "act", "exp", "iat", "iss", "aud", "jti", "cnf" 059 )) 060 ); 061 062 063 @Override 064 public JWTClaimsSet encode(final AccessTokenAuthorization tokenAuthz, final TokenEncoderContext context) { 065 066 JWTClaimsSet.Builder builder = new JWTClaimsSet.Builder(); 067 068 if (tokenAuthz.getSubject() != null) builder.subject(tokenAuthz.getSubject().getValue()); 069 if (tokenAuthz.getActor() != null) builder.claim("act", tokenAuthz.getActor().toJSONObject()); 070 if (tokenAuthz.getExpirationTime() != null) builder.expirationTime(Date.from(tokenAuthz.getExpirationTime())); 071 if (tokenAuthz.getIssueTime() != null) builder.issueTime(Date.from(tokenAuthz.getIssueTime())); 072 if (tokenAuthz.getIssuer() != null) builder.issuer(tokenAuthz.getIssuer().getValue()); 073 if (tokenAuthz.getAudienceList() != null) builder.audience(Audience.toStringList(tokenAuthz.getAudienceList())); 074 if (tokenAuthz.getJWTID() != null) builder.jwtID(tokenAuthz.getJWTID().getValue()); 075 if (tokenAuthz.getClientCertificateConfirmation() != null) { 076 Map.Entry<String, JSONObject> cnf = tokenAuthz.getClientCertificateConfirmation().toJWTClaim(); 077 builder.claim(cnf.getKey(), cnf.getValue()); 078 } 079 080 return builder.build(); 081 } 082 083 084 @Override 085 public JWTDetails advancedEncode(final AccessTokenAuthorization tokenAuthz, final TokenEncoderContext context) { 086 return new JWTDetails() { 087 @Override 088 public JOSEObjectType getType() { 089 return null; 090 } 091 092 093 @Override 094 public JWTClaimsSet getJWTClaimsSet() { 095 return encode(tokenAuthz, context); 096 } 097 }; 098 } 099 100 101 @Override 102 public AccessTokenAuthorization decode(final JWTClaimsSet claimsSet, final TokenCodecContext context) 103 throws TokenDecodeException { 104 105 MutableAccessTokenAuthorization authz = new MutableAccessTokenAuthorization(); 106 107 String sub = claimsSet.getSubject(); 108 if (sub != null) authz.withSubject(new Subject(sub)); 109 110 try { 111 JSONObject act = claimsSet.getJSONObjectClaim("act"); 112 if (act != null) authz.withActor(Actor.parse(act)); 113 } catch (Exception e) { 114 throw new TokenDecodeException("Couldn't parse actor: " + e.getMessage(), e); 115 } 116 117 Date exp = claimsSet.getExpirationTime(); 118 if (exp != null) authz.withExpirationTime(exp.toInstant()); 119 120 Date iat = claimsSet.getIssueTime(); 121 if (iat != null) authz.withIssueTime(iat.toInstant()); 122 123 String iss = claimsSet.getIssuer(); 124 if (iss != null) authz.withIssuer(new Issuer(iss)); 125 126 List<String> aud = claimsSet.getAudience(); 127 if (aud != null && ! aud.isEmpty()) authz.withAudienceList(Audience.create(aud)); 128 129 String jti = claimsSet.getJWTID(); 130 if (jti != null) authz.withJWTID(new JWTID(jti)); 131 132 authz.withClientCertificateConfirmation(X509CertificateConfirmation.parse(claimsSet)); 133 134 return authz; 135 } 136 137 138 @Override 139 public AccessTokenAuthorization advancedDecode(final JWTDetails jwtDetails, final TokenCodecContext context) 140 throws TokenDecodeException { 141 142 return decode(jwtDetails.getJWTClaimsSet(), context); 143 } 144}