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