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