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