001package com.nimbusds.oauth2.sdk; 002 003 004import java.util.*; 005 006import net.jcip.annotations.Immutable; 007 008import com.nimbusds.jose.*; 009import com.nimbusds.jwt.*; 010 011 012/** 013 * JWT bearer grant. Used in access token requests with a JSON Web Token (JWT), 014 * such an OpenID Connect ID token. 015 * 016 * <p>The JWT assertion can be: 017 * 018 * <ul> 019 * <li>Signed or MAC protected with JWS 020 * <li>Encrypted with JWE 021 * <li>Nested - signed / MAC protected with JWS and then encrypted with JWE 022 * </ul> 023 * 024 * <p>Related specifications: 025 * 026 * <ul> 027 * <li>Assertion Framework for OAuth 2.0 Client Authentication and 028 * Authorization Grants (RFC 7521), section 4.1. 029 * <li>JSON Web Token (JWT) Profile for OAuth 2.0 Client Authentication and 030 * Authorization Grants (RFC 7523), section-2.1. 031 * </ul> 032 */ 033@Immutable 034public class JWTBearerGrant extends AssertionGrant { 035 036 037 /** 038 * The grant type. 039 */ 040 public static final GrantType GRANT_TYPE = GrantType.JWT_BEARER; 041 042 043 /** 044 * Cached {@code unsupported_grant_type} exception. 045 */ 046 private static final ParseException UNSUPPORTED_GRANT_TYPE_EXCEPTION 047 = new ParseException("The \"grant_type\" must be " + GRANT_TYPE, OAuth2Error.UNSUPPORTED_GRANT_TYPE); 048 049 050 /** 051 * Cached plain JOSE / JWT rejected exception. 052 */ 053 private static final ParseException PLAIN_ASSERTION_REJECTED_EXCEPTION 054 = new ParseException("The JWT assertion must not be unsecured (plain)", OAuth2Error.INVALID_REQUEST); 055 056 057 /** 058 * Cached JWT assertion parse exception. 059 */ 060 private static final ParseException JWT_PARSE_EXCEPTION 061 = new ParseException("The \"assertion\" is not a JWT", OAuth2Error.INVALID_REQUEST); 062 063 /** 064 * The assertion - signed JWT, encrypted JWT or nested signed+encrypted 065 * JWT. 066 */ 067 private final JOSEObject assertion; 068 069 070 /** 071 * Creates a new signed JSON Web Token (JWT) bearer assertion grant. 072 * 073 * @param assertion The signed JSON Web Token (JWT) assertion. Must not 074 * be in a unsigned state or {@code null}. The JWT 075 * claims are not validated for compliance with the 076 * standard. 077 */ 078 public JWTBearerGrant(final SignedJWT assertion) { 079 080 super(GRANT_TYPE); 081 082 if (assertion.getState().equals(JWSObject.State.UNSIGNED)) 083 throw new IllegalArgumentException("The JWT assertion must not be in a unsigned state"); 084 085 this.assertion = assertion; 086 } 087 088 089 /** 090 * Creates a new nested signed and encrypted JSON Web Token (JWT) 091 * bearer assertion grant. 092 * 093 * @param assertion The nested signed and encrypted JSON Web Token 094 * (JWT) assertion. Must not be in a unencrypted state 095 * or {@code null}. The JWT claims are not validated 096 * for compliance with the standard. 097 */ 098 public JWTBearerGrant(final JWEObject assertion) { 099 100 super(GRANT_TYPE); 101 102 if (assertion.getState().equals(JWEObject.State.UNENCRYPTED)) 103 throw new IllegalArgumentException("The JWT assertion must not be in a unencrypted state"); 104 105 this.assertion = assertion; 106 } 107 108 109 /** 110 * Creates a new signed and encrypted JSON Web Token (JWT) bearer 111 * assertion grant. 112 * 113 * @param assertion The signed and encrypted JSON Web Token (JWT) 114 * assertion. Must not be in a unencrypted state or 115 * {@code null}. The JWT claims are not validated for 116 * compliance with the standard. 117 */ 118 public JWTBearerGrant(final EncryptedJWT assertion) { 119 120 this((JWEObject) assertion); 121 } 122 123 124 /** 125 * Gets the JSON Web Token (JWT) bearer assertion. 126 * 127 * @return The assertion as a signed or encrypted JWT, {@code null} if 128 * the assertion is a signed and encrypted JWT. 129 */ 130 public JWT getJWTAssertion() { 131 132 return assertion instanceof JWT ? (JWT)assertion : null; 133 } 134 135 136 /** 137 * Gets the JSON Web Token (JWT) bearer assertion. 138 * 139 * @return The assertion as a generic JOSE object (signed JWT, 140 * encrypted JWT, or signed and encrypted JWT). 141 */ 142 public JOSEObject getJOSEAssertion() { 143 144 return assertion; 145 } 146 147 148 @Override 149 public String getAssertion() { 150 151 return assertion.serialize(); 152 } 153 154 155 @Override 156 public Map<String,String> toParameters() { 157 158 Map<String,String> params = new LinkedHashMap<>(); 159 params.put("grant_type", GRANT_TYPE.getValue()); 160 params.put("assertion", assertion.serialize()); 161 return params; 162 } 163 164 165 /** 166 * Parses a JWT bearer grant from the specified parameters. The JWT 167 * claims are not validated for compliance with the standard. 168 * 169 * <p>Example: 170 * 171 * <pre> 172 * grant_type=urn%3Aietf%3Aparams%3Aoauth%3Agrant-type%3Ajwt-bearer 173 * &assertion=eyJhbGciOiJFUzI1NiJ9.eyJpc3Mi[...omitted for brevity...]. 174 * J9l-ZhwP[...omitted for brevity...] 175 * </pre> 176 * 177 * @param params The parameters. 178 * 179 * @return The JWT bearer grant. 180 * 181 * @throws ParseException If parsing failed. 182 */ 183 public static JWTBearerGrant parse(final Map<String,String> params) 184 throws ParseException { 185 186 // Parse grant type 187 String grantTypeString = params.get("grant_type"); 188 189 if (grantTypeString == null) 190 throw MISSING_GRANT_TYPE_PARAM_EXCEPTION; 191 192 if (! GrantType.parse(grantTypeString).equals(GRANT_TYPE)) 193 throw UNSUPPORTED_GRANT_TYPE_EXCEPTION; 194 195 // Parse JWT assertion 196 String assertionString = params.get("assertion"); 197 198 if (assertionString == null || assertionString.trim().isEmpty()) 199 throw MISSING_ASSERTION_PARAM_EXCEPTION; 200 201 try { 202 final JOSEObject assertion = JOSEObject.parse(assertionString); 203 204 if (assertion instanceof PlainObject) { 205 206 throw PLAIN_ASSERTION_REJECTED_EXCEPTION; 207 208 } else if (assertion instanceof JWSObject) { 209 210 return new JWTBearerGrant(new SignedJWT( 211 assertion.getParsedParts()[0], 212 assertion.getParsedParts()[1], 213 assertion.getParsedParts()[2])); 214 215 } else { 216 // JWE 217 218 if ("JWT".equalsIgnoreCase(assertion.getHeader().getContentType())) { 219 // Assume nested: signed JWT inside JWE 220 // http://tools.ietf.org/html/rfc7519#section-5.2 221 return new JWTBearerGrant((JWEObject)assertion); 222 } else { 223 // Assume encrypted JWT 224 return new JWTBearerGrant(new EncryptedJWT( 225 assertion.getParsedParts()[0], 226 assertion.getParsedParts()[1], 227 assertion.getParsedParts()[2], 228 assertion.getParsedParts()[3], 229 assertion.getParsedParts()[4])); 230 } 231 } 232 233 } catch (java.text.ParseException e) { 234 throw JWT_PARSE_EXCEPTION; 235 } 236 } 237}