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