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 /** 061 * Cached {@code unsupported_grant_type} exception. 062 */ 063 private static final ParseException UNSUPPORTED_GRANT_TYPE_EXCEPTION 064 = new ParseException("The \"grant_type\" must be " + GRANT_TYPE, OAuth2Error.UNSUPPORTED_GRANT_TYPE); 065 066 067 /** 068 * Cached plain JOSE / JWT rejected exception. 069 */ 070 private static final ParseException PLAIN_ASSERTION_REJECTED_EXCEPTION 071 = new ParseException("The JWT assertion must not be unsecured (plain)", OAuth2Error.INVALID_REQUEST); 072 073 074 /** 075 * Cached JWT assertion parse exception. 076 */ 077 private static final ParseException JWT_PARSE_EXCEPTION 078 = new ParseException("The \"assertion\" is not a JWT", OAuth2Error.INVALID_REQUEST); 079 080 /** 081 * The assertion - signed JWT, encrypted JWT or nested signed+encrypted 082 * JWT. 083 */ 084 private final JOSEObject assertion; 085 086 087 /** 088 * Creates a new signed JSON Web Token (JWT) bearer assertion grant. 089 * 090 * @param assertion The signed JSON Web Token (JWT) assertion. Must not 091 * be in a unsigned state or {@code null}. The JWT 092 * claims are not validated for compliance with the 093 * standard. 094 */ 095 public JWTBearerGrant(final SignedJWT assertion) { 096 097 super(GRANT_TYPE); 098 099 if (assertion.getState().equals(JWSObject.State.UNSIGNED)) 100 throw new IllegalArgumentException("The JWT assertion must not be in a unsigned state"); 101 102 this.assertion = assertion; 103 } 104 105 106 /** 107 * Creates a new nested signed and encrypted JSON Web Token (JWT) 108 * bearer assertion grant. 109 * 110 * @param assertion The nested signed and encrypted JSON Web Token 111 * (JWT) assertion. Must not be in a unencrypted state 112 * or {@code null}. The JWT claims are not validated 113 * for compliance with the standard. 114 */ 115 public JWTBearerGrant(final JWEObject assertion) { 116 117 super(GRANT_TYPE); 118 119 if (assertion.getState().equals(JWEObject.State.UNENCRYPTED)) 120 throw new IllegalArgumentException("The JWT assertion must not be in a unencrypted state"); 121 122 this.assertion = assertion; 123 } 124 125 126 /** 127 * Creates a new signed and encrypted JSON Web Token (JWT) bearer 128 * assertion grant. 129 * 130 * @param assertion The signed and encrypted JSON Web Token (JWT) 131 * assertion. Must not be in a unencrypted state or 132 * {@code null}. The JWT claims are not validated for 133 * compliance with the standard. 134 */ 135 public JWTBearerGrant(final EncryptedJWT assertion) { 136 137 this((JWEObject) assertion); 138 } 139 140 141 /** 142 * Gets the JSON Web Token (JWT) bearer assertion. 143 * 144 * @return The assertion as a signed or encrypted JWT, {@code null} if 145 * the assertion is a signed and encrypted JWT. 146 */ 147 public JWT getJWTAssertion() { 148 149 return assertion instanceof JWT ? (JWT)assertion : null; 150 } 151 152 153 /** 154 * Gets the JSON Web Token (JWT) bearer assertion. 155 * 156 * @return The assertion as a generic JOSE object (signed JWT, 157 * encrypted JWT, or signed and encrypted JWT). 158 */ 159 public JOSEObject getJOSEAssertion() { 160 161 return assertion; 162 } 163 164 165 @Override 166 public String getAssertion() { 167 168 return assertion.serialize(); 169 } 170 171 172 @Override 173 public Map<String,String> toParameters() { 174 175 Map<String,String> params = new LinkedHashMap<>(); 176 params.put("grant_type", GRANT_TYPE.getValue()); 177 params.put("assertion", assertion.serialize()); 178 return params; 179 } 180 181 182 /** 183 * Parses a JWT bearer grant from the specified parameters. The JWT 184 * claims are not validated for compliance with the standard. 185 * 186 * <p>Example: 187 * 188 * <pre> 189 * grant_type=urn%3Aietf%3Aparams%3Aoauth%3Agrant-type%3Ajwt-bearer 190 * &assertion=eyJhbGciOiJFUzI1NiJ9.eyJpc3Mi[...omitted for brevity...]. 191 * J9l-ZhwP[...omitted for brevity...] 192 * </pre> 193 * 194 * @param params The parameters. 195 * 196 * @return The JWT bearer grant. 197 * 198 * @throws ParseException If parsing failed. 199 */ 200 public static JWTBearerGrant parse(final Map<String,String> params) 201 throws ParseException { 202 203 // Parse grant type 204 String grantTypeString = params.get("grant_type"); 205 206 if (grantTypeString == null) 207 throw MISSING_GRANT_TYPE_PARAM_EXCEPTION; 208 209 if (! GrantType.parse(grantTypeString).equals(GRANT_TYPE)) 210 throw UNSUPPORTED_GRANT_TYPE_EXCEPTION; 211 212 // Parse JWT assertion 213 String assertionString = params.get("assertion"); 214 215 if (assertionString == null || assertionString.trim().isEmpty()) 216 throw MISSING_ASSERTION_PARAM_EXCEPTION; 217 218 try { 219 final JOSEObject assertion = JOSEObject.parse(assertionString); 220 221 if (assertion instanceof PlainObject) { 222 223 throw PLAIN_ASSERTION_REJECTED_EXCEPTION; 224 225 } else if (assertion instanceof JWSObject) { 226 227 return new JWTBearerGrant(new SignedJWT( 228 assertion.getParsedParts()[0], 229 assertion.getParsedParts()[1], 230 assertion.getParsedParts()[2])); 231 232 } else { 233 // JWE 234 235 if ("JWT".equalsIgnoreCase(assertion.getHeader().getContentType())) { 236 // Assume nested: signed JWT inside JWE 237 // http://tools.ietf.org/html/rfc7519#section-5.2 238 return new JWTBearerGrant((JWEObject)assertion); 239 } else { 240 // Assume encrypted JWT 241 return new JWTBearerGrant(new EncryptedJWT( 242 assertion.getParsedParts()[0], 243 assertion.getParsedParts()[1], 244 assertion.getParsedParts()[2], 245 assertion.getParsedParts()[3], 246 assertion.getParsedParts()[4])); 247 } 248 } 249 250 } catch (java.text.ParseException e) { 251 throw JWT_PARSE_EXCEPTION; 252 } 253 } 254}