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