001package com.nimbusds.openid.connect.provider.spi.tokens; 002 003 004import java.util.Date; 005import java.util.List; 006import java.util.Map; 007 008import com.nimbusds.jwt.JWTClaimsSet; 009import com.nimbusds.oauth2.sdk.auth.X509CertificateConfirmation; 010import com.nimbusds.oauth2.sdk.id.*; 011import net.jcip.annotations.ThreadSafe; 012import net.minidev.json.JSONObject; 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-07): 022 * 023 * <ul> 024 * <li>subject - "sub" 025 * <li>actor - "act" 026 * <li>expiration time - "exp" 027 * <li>issue time - "iat" 028 * <li>audience - "aud" 029 * <li>JWT ID - "jti" 030 * <li>client X.509 certificate SHA-256 thumbprint (mTLS) - "cnf.x5t#S256" 031 * </ul> 032 * 033 * <p>The extending class should implement encoding and decoding for the 034 * remaining token parameters: 035 * 036 * <ul> 037 * <li>client ID 038 * <li>scope 039 * <li>consented OpenID claim names 040 * <li>preferred claims locales 041 * <li>preset OpenID claims 042 * <li>additional data 043 * </ul> 044 */ 045@ThreadSafe 046public abstract class BaseSelfContainedAccessTokenClaimsCodec implements SelfContainedAccessTokenClaimsCodec { 047 048 049 @Override 050 public JWTClaimsSet encode(final AccessTokenAuthorization tokenAuthz, final TokenEncoderContext context) { 051 052 JWTClaimsSet.Builder builder = new JWTClaimsSet.Builder(); 053 054 if (tokenAuthz.getSubject() != null) builder.subject(tokenAuthz.getSubject().getValue()); 055 if (tokenAuthz.getActor() != null) builder.claim("act", tokenAuthz.getActor().toJSONObject()); 056 if (tokenAuthz.getExpirationTime() != null) builder.expirationTime(Date.from(tokenAuthz.getExpirationTime())); 057 if (tokenAuthz.getIssueTime() != null) builder.issueTime(Date.from(tokenAuthz.getIssueTime())); 058 if (tokenAuthz.getIssuer() != null) builder.issuer(tokenAuthz.getIssuer().getValue()); 059 if (tokenAuthz.getAudienceList() != null) builder.audience(Audience.toStringList(tokenAuthz.getAudienceList())); 060 if (tokenAuthz.getJWTID() != null) builder.jwtID(tokenAuthz.getJWTID().getValue()); 061 if (tokenAuthz.getClientCertificateConfirmation() != null) { 062 Map.Entry<String, JSONObject> cnf = tokenAuthz.getClientCertificateConfirmation().toJWTClaim(); 063 builder.claim(cnf.getKey(), cnf.getValue()); 064 } 065 066 return builder.build(); 067 } 068 069 070 @Override 071 public AccessTokenAuthorization decode(final JWTClaimsSet claimsSet, final TokenCodecContext context) 072 throws TokenDecodeException { 073 074 MutableAccessTokenAuthorization authz = new MutableAccessTokenAuthorization(); 075 076 String sub = claimsSet.getSubject(); 077 if (sub != null) authz.withSubject(new Subject(sub)); 078 079 try { 080 JSONObject act = claimsSet.getJSONObjectClaim("act"); 081 if (act != null) authz.withActor(Actor.parse(act)); 082 } catch (Exception e) { 083 throw new TokenDecodeException("Couldn't parse actor: " + e.getMessage(), e); 084 } 085 086 Date exp = claimsSet.getExpirationTime(); 087 if (exp != null) authz.withExpirationTime(exp.toInstant()); 088 089 Date iat = claimsSet.getIssueTime(); 090 if (iat != null) authz.withIssueTime(iat.toInstant()); 091 092 String iss = claimsSet.getIssuer(); 093 if (iss != null) authz.withIssuer(new Issuer(iss)); 094 095 List<String> aud = claimsSet.getAudience(); 096 if (aud != null && ! aud.isEmpty()) authz.withAudienceList(Audience.create(aud)); 097 098 String jti = claimsSet.getJWTID(); 099 if (jti != null) authz.withJWTID(new JWTID(jti)); 100 101 authz.withClientCertificateConfirmation(X509CertificateConfirmation.parse(claimsSet)); 102 103 return authz; 104 } 105}