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, draft-ietf-oauth-mtls-17, section 3.1): 022 * 023 * <ul> 024 * <li>{@link AccessTokenAuthorization#getSubject subject} - "sub" 025 * <li>{@link AccessTokenAuthorization#getActor actor} - "act" 026 * <li>{@link AccessTokenAuthorization#getExpirationTime expiration time} - "exp" 027 * <li>{@link AccessTokenAuthorization#getIssueTime issue time} - "iat" 028 * <li>{@link AccessTokenAuthorization#getIssuer issuer} - "iss" 029 * <li>{@link AccessTokenAuthorization#getAudienceList audience} - "aud" 030 * <li>{@link AccessTokenAuthorization#getJWTID JWT ID} - "jti" 031 * <li>{@link AccessTokenAuthorization#getClientCertificateConfirmation client X.509 certificate SHA-256 thumbprint (mTLS)} - "cnf.x5t#S256" 032 * </ul> 033 * 034 * <p>The extending class should implement encoding and decoding for the 035 * remaining token parameters: 036 * 037 * <ul> 038 * <li>{@link AccessTokenAuthorization#getClientID client ID} 039 * <li>{@link AccessTokenAuthorization#getScope scope} 040 * <li>{@link AccessTokenAuthorization#getClaimNames consented OpenID claim names} 041 * <li>{@link AccessTokenAuthorization#getClaimsLocales preferred claims locales} 042 * <li>{@link AccessTokenAuthorization#getPresetClaims preset OpenID claims} 043 * <li>{@link AccessTokenAuthorization#getData additional data} 044 * <li>{@link AccessTokenAuthorization#getOtherTopLevelParameters other top-level parameters} 045 * </ul> 046 */ 047@ThreadSafe 048public abstract class BaseSelfContainedAccessTokenClaimsCodec implements SelfContainedAccessTokenClaimsCodec { 049 050 051 /** 052 * The supported claim names. 053 */ 054 public static final Set<String> SUPPORTED_CLAIM_NAMES = Collections.unmodifiableSet( 055 new HashSet<>(Arrays.asList( 056 "sub", "act", "exp", "iat", "iss", "aud", "jti", "cnf" 057 )) 058 ); 059 060 061 @Override 062 public JWTClaimsSet encode(final AccessTokenAuthorization tokenAuthz, final TokenEncoderContext context) { 063 064 JWTClaimsSet.Builder builder = new JWTClaimsSet.Builder(); 065 066 if (tokenAuthz.getSubject() != null) builder.subject(tokenAuthz.getSubject().getValue()); 067 if (tokenAuthz.getActor() != null) builder.claim("act", tokenAuthz.getActor().toJSONObject()); 068 if (tokenAuthz.getExpirationTime() != null) builder.expirationTime(Date.from(tokenAuthz.getExpirationTime())); 069 if (tokenAuthz.getIssueTime() != null) builder.issueTime(Date.from(tokenAuthz.getIssueTime())); 070 if (tokenAuthz.getIssuer() != null) builder.issuer(tokenAuthz.getIssuer().getValue()); 071 if (tokenAuthz.getAudienceList() != null) builder.audience(Audience.toStringList(tokenAuthz.getAudienceList())); 072 if (tokenAuthz.getJWTID() != null) builder.jwtID(tokenAuthz.getJWTID().getValue()); 073 if (tokenAuthz.getClientCertificateConfirmation() != null) { 074 Map.Entry<String, JSONObject> cnf = tokenAuthz.getClientCertificateConfirmation().toJWTClaim(); 075 builder.claim(cnf.getKey(), cnf.getValue()); 076 } 077 078 return builder.build(); 079 } 080 081 082 @Override 083 public JWTDetails advancedEncode(final AccessTokenAuthorization tokenAuthz, final TokenEncoderContext context) { 084 return new JWTDetails() { 085 @Override 086 public JOSEObjectType getType() { 087 return null; 088 } 089 090 091 @Override 092 public JWTClaimsSet getJWTClaimsSet() { 093 return encode(tokenAuthz, context); 094 } 095 }; 096 } 097 098 099 @Override 100 public AccessTokenAuthorization decode(final JWTClaimsSet claimsSet, final TokenCodecContext context) 101 throws TokenDecodeException { 102 103 MutableAccessTokenAuthorization authz = new MutableAccessTokenAuthorization(); 104 105 String sub = claimsSet.getSubject(); 106 if (sub != null) authz.withSubject(new Subject(sub)); 107 108 try { 109 JSONObject act = claimsSet.getJSONObjectClaim("act"); 110 if (act != null) authz.withActor(Actor.parse(act)); 111 } catch (Exception e) { 112 throw new TokenDecodeException("Couldn't parse actor: " + e.getMessage(), e); 113 } 114 115 Date exp = claimsSet.getExpirationTime(); 116 if (exp != null) authz.withExpirationTime(exp.toInstant()); 117 118 Date iat = claimsSet.getIssueTime(); 119 if (iat != null) authz.withIssueTime(iat.toInstant()); 120 121 String iss = claimsSet.getIssuer(); 122 if (iss != null) authz.withIssuer(new Issuer(iss)); 123 124 List<String> aud = claimsSet.getAudience(); 125 if (aud != null && ! aud.isEmpty()) authz.withAudienceList(Audience.create(aud)); 126 127 String jti = claimsSet.getJWTID(); 128 if (jti != null) authz.withJWTID(new JWTID(jti)); 129 130 authz.withClientCertificateConfirmation(X509CertificateConfirmation.parse(claimsSet)); 131 132 return authz; 133 } 134 135 136 @Override 137 public AccessTokenAuthorization advancedDecode(final JWTDetails jwtDetails, final TokenCodecContext context) 138 throws TokenDecodeException { 139 140 return decode(jwtDetails.getJWTClaimsSet(), context); 141 } 142}