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.token; 019 020 021import java.util.List; 022import java.util.Map; 023 024import com.nimbusds.oauth2.sdk.ParseException; 025import com.nimbusds.oauth2.sdk.Scope; 026import com.nimbusds.oauth2.sdk.http.HTTPRequest; 027import com.nimbusds.oauth2.sdk.util.JSONObjectUtils; 028import com.nimbusds.oauth2.sdk.util.MultivaluedMapUtils; 029import com.nimbusds.oauth2.sdk.util.StringUtils; 030import net.jcip.annotations.Immutable; 031import net.minidev.json.JSONObject; 032 033 034/** 035 * Bearer access token. 036 * 037 * <p>Example bearer access token serialised to JSON: 038 * 039 * <pre> 040 * { 041 * "access_token" : "2YotnFZFEjr1zCsicMWpAA", 042 * "token_type" : "bearer", 043 * "expires_in" : 3600, 044 * "scope" : "read write" 045 * } 046 * </pre> 047 * 048 * <p>The above example token serialised to a HTTP Authorization header: 049 * 050 * <pre> 051 * Authorization: Bearer 2YotnFZFEjr1zCsicMWpAA 052 * </pre> 053 * 054 * <p>Related specifications: 055 * 056 * <ul> 057 * <li>OAuth 2.0 (RFC 6749), sections 1.4 and 5.1. 058 * <li>OAuth 2.0 Bearer Token Usage (RFC 6750). 059 * </ul> 060 */ 061@Immutable 062public class BearerAccessToken extends AccessToken { 063 064 065 /** 066 * Creates a new minimal bearer access token with a randomly generated 067 * 256-bit (32-byte) value, Base64URL-encoded. The optional lifetime 068 * and scope are left undefined. 069 */ 070 public BearerAccessToken() { 071 072 this(32); 073 } 074 075 076 /** 077 * Creates a new minimal bearer access token with a randomly generated 078 * value of the specified byte length, Base64URL-encoded. The optional 079 * lifetime and scope are left undefined. 080 * 081 * @param byteLength The byte length of the value to generate. Must be 082 * greater than one. 083 */ 084 public BearerAccessToken(final int byteLength) { 085 086 this(byteLength, 0L, null); 087 } 088 089 090 /** 091 * Creates a new bearer access token with a randomly generated 256-bit 092 * (32-byte) value, Base64URL-encoded. 093 * 094 * @param lifetime The lifetime in seconds, 0 if not specified. 095 * @param scope The scope, {@code null} if not specified. 096 */ 097 public BearerAccessToken(final long lifetime, final Scope scope) { 098 099 this(32, lifetime, scope); 100 } 101 102 103 /** 104 * Creates a new bearer access token with a randomly generated value of 105 * the specified byte length, Base64URL-encoded. 106 * 107 * @param byteLength The byte length of the value to generate. Must be 108 * greater than one. 109 * @param lifetime The lifetime in seconds, 0 if not specified. 110 * @param scope The scope, {@code null} if not specified. 111 */ 112 public BearerAccessToken(final int byteLength, final long lifetime, final Scope scope) { 113 114 super(AccessTokenType.BEARER, byteLength, lifetime, scope); 115 } 116 117 118 /** 119 * Creates a new minimal bearer access token with the specified value. 120 * The optional lifetime and scope are left undefined. 121 * 122 * @param value The access token value. Must not be {@code null} or 123 * empty string. 124 */ 125 public BearerAccessToken(final String value) { 126 127 this(value, 0L, null); 128 } 129 130 131 /** 132 * Creates a new bearer access token with the specified value and 133 * optional lifetime and scope. 134 * 135 * @param value The access token value. Must not be {@code null} or 136 * empty string. 137 * @param lifetime The lifetime in seconds, 0 if not specified. 138 * @param scope The scope, {@code null} if not specified. 139 */ 140 public BearerAccessToken(final String value, final long lifetime, final Scope scope) { 141 142 super(AccessTokenType.BEARER, value, lifetime, scope); 143 } 144 145 146 /** 147 * Returns the HTTP Authorization header value for this bearer access 148 * token. 149 * 150 * <p>Example: 151 * 152 * <pre> 153 * Authorization: Bearer eyJhbGciOiJIUzI1NiJ9 154 * </pre> 155 * 156 * @return The HTTP Authorization header. 157 */ 158 @Override 159 public String toAuthorizationHeader(){ 160 161 return "Bearer " + getValue(); 162 } 163 164 165 @Override 166 public boolean equals(final Object object) { 167 168 return object instanceof BearerAccessToken && 169 this.toString().equals(object.toString()); 170 } 171 172 173 /** 174 * Parses a bearer access token from a JSON object access token 175 * response. 176 * 177 * @param jsonObject The JSON object to parse. Must not be 178 * {@code null}. 179 * 180 * @return The bearer access token. 181 * 182 * @throws ParseException If the JSON object couldn't be parsed to a 183 * bearer access token. 184 */ 185 public static BearerAccessToken parse(final JSONObject jsonObject) 186 throws ParseException { 187 188 // Parse and verify type 189 AccessTokenType tokenType = new AccessTokenType(JSONObjectUtils.getString(jsonObject, "token_type")); 190 191 if (! tokenType.equals(AccessTokenType.BEARER)) 192 throw new ParseException("Token type must be Bearer"); 193 194 195 // Parse value 196 String accessTokenValue = JSONObjectUtils.getString(jsonObject, "access_token"); 197 198 199 // Parse lifetime 200 long lifetime = 0; 201 202 if (jsonObject.containsKey("expires_in")) { 203 204 // Lifetime can be a JSON number or string 205 206 if (jsonObject.get("expires_in") instanceof Number) { 207 208 lifetime = JSONObjectUtils.getLong(jsonObject, "expires_in"); 209 } 210 else { 211 String lifetimeStr = JSONObjectUtils.getString(jsonObject, "expires_in"); 212 213 try { 214 lifetime = Long.parseLong(lifetimeStr); 215 216 } catch (NumberFormatException e) { 217 218 throw new ParseException("Invalid expires_in parameter, must be integer"); 219 } 220 } 221 } 222 223 224 // Parse scope 225 Scope scope = Scope.parse(JSONObjectUtils.getString(jsonObject, "scope", null)); 226 227 228 return new BearerAccessToken(accessTokenValue, lifetime, scope); 229 } 230 231 232 /** 233 * Parses an HTTP Authorization header for a bearer access token. 234 * 235 * @param header The HTTP Authorization header value to parse. May be 236 * {@code null} if the header is missing, in which case 237 * an exception will be thrown. 238 * 239 * @return The bearer access token. 240 * 241 * @throws ParseException If the HTTP Authorization header value 242 * couldn't be parsed to a bearer access token. 243 */ 244 public static BearerAccessToken parse(final String header) 245 throws ParseException { 246 247 if (StringUtils.isBlank(header)) 248 throw new ParseException("Missing HTTP Authorization header", BearerTokenError.MISSING_TOKEN); 249 250 String[] parts = header.split("\\s", 2); 251 252 if (parts.length != 2) 253 throw new ParseException("Invalid HTTP Authorization header value", BearerTokenError.INVALID_REQUEST); 254 255 if (! parts[0].equals("Bearer")) 256 throw new ParseException("Token type must be Bearer", BearerTokenError.INVALID_REQUEST); 257 258 try { 259 return new BearerAccessToken(parts[1]); 260 261 } catch (IllegalArgumentException e) { 262 263 throw new ParseException(e.getMessage(), BearerTokenError.INVALID_REQUEST); 264 } 265 } 266 267 268 /** 269 * Parses a query or form parameters map for a bearer access token. 270 * 271 * @param parameters The query parameters. Must not be {@code null}. 272 * 273 * @return The bearer access token. 274 * 275 * @throws ParseException If a bearer access token wasn't found in the 276 * parameters. 277 */ 278 public static BearerAccessToken parse(final Map<String,List<String>> parameters) 279 throws ParseException { 280 281 if (! parameters.containsKey("access_token")) { 282 throw new ParseException("Missing access token parameter", BearerTokenError.MISSING_TOKEN); 283 } 284 285 String accessTokenValue = MultivaluedMapUtils.getFirstValue(parameters, "access_token"); 286 287 if (StringUtils.isBlank(accessTokenValue)) { 288 throw new ParseException("Blank / empty access token", BearerTokenError.INVALID_REQUEST); 289 } 290 291 return new BearerAccessToken(accessTokenValue); 292 } 293 294 295 296 /** 297 * Parses an HTTP request for a bearer access token. 298 * 299 * @param request The HTTP request to parse. Must not be {@code null}. 300 * 301 * @return The bearer access token. 302 * 303 * @throws ParseException If a bearer access token wasn't found in the 304 * HTTP request. 305 */ 306 public static BearerAccessToken parse(final HTTPRequest request) 307 throws ParseException { 308 309 // See http://tools.ietf.org/html/rfc6750#section-2 310 311 String authzHeader = request.getAuthorization(); 312 313 if (authzHeader != null) { 314 315 return parse(authzHeader); 316 } 317 318 // Try alternative token locations, form and query string are 319 // parameters are not differentiated here 320 321 Map<String,List<String>> params = request.getQueryParameters(); 322 323 return parse(params); 324 } 325}